Add Drop-Down List and Combo Box Content Controls to Word Documents with JavaScript in React

Fields such as evaluation conclusions, department names and product line ownership usually take only a limited set of values. If the person filling in the form is allowed to type freely, it is easy to end up with "R&D Department", "R&D Center" and "R&D" used side by side, and the records have to be cleaned up one by one before they can be summarized. Word's drop-down list and combo box content controls write the allowed values into the document in advance, so the person filling in the form can only pick from them, and the data format is unified at the source.

The difference between the two lies in whether values outside the preset range are allowed: a drop-down list accepts only the options in the list, while a combo box also allows the user to enter something outside it. Which one to choose depends on whether the business needs to keep exceptions.

This article uses Spire.Doc for JavaScript to create both kinds of controls in the browser, and demonstrates how to update the list items and selected value of an existing combo box. All processing is performed in WebAssembly; documents never pass through a server.

This article covers the following topics:

Both kinds of controls mount their list data through SDTProperties.ControlProperties, and SdtListItem is a single item in the list:

Control type SDTType value Control property class Allows custom input
Drop-down list content control SdtType.DropDownList SdtDropDownList No
Combo box content control SdtType.ComboBox SdtComboBox Yes

For installation and project setup, refer to Integrating Spire.Doc for JavaScript in a React Project. The examples below assume that Spire.Doc is installed and the WebAssembly module is initialized.


Add a Drop-Down List Content Control

A drop-down list suits fields whose values are completely fixed, such as rating levels, approval conclusions and expense categories. When creating one, first set SDTType to SdtType.DropDownList, then construct an SdtDropDownList and add each option to ListItems as an SdtListItem.

The following members are involved in adding a drop-down list content control:

Member Purpose
SdtType.DropDownList Sets the control type to drop-down list
SdtDropDownList Control properties object of the drop-down list
ListItems.Add(item) Appends an option to the list
SdtListItem(displayText) Creates an option whose display text and value are the same
SdtListItem(displayText, value) Creates an option with separate display text and value
ListItems.get_Item(index) Gets an option by index, starting at 0

The specific steps are as follows:

  1. Get the Spire.Doc WASM module and confirm that it is ready.
  2. Load the font, create a document with the Document class and add a section, then write the title and description.
  3. Create a StructureDocumentTagInline control in the paragraph and set SDTType to SdtType.DropDownList.
  4. Create an SdtDropDownList, add the SdtListItem options one by one, and assign it to ControlProperties.
  5. Write the text of the currently selected option into SDTContent, save the file and read it from the VFS for download.

The code for adding a drop-down list content control is as follows:

function App() {
  const addDropDownList = async () => {
    // Get the Spire.Doc WASM module
    const wasmModule = window.wasmModule?.spiredoc;

    if (!wasmModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Load the font
    await window.spire.FetchFileToVFS('arial.ttf', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    await window.spire.FetchFileToVFS('arialbd.ttf', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);

    // Create a document and add a section
    const doc = new wasmModule.Document();
    const section = doc.AddSection();

    // Add the title and subtitle
    const titleParagraph = section.AddParagraph();
    const titleRange = titleParagraph.AppendText('Supplier Rating Form');
    titleRange.CharacterFormat.FontName = 'Arial';
    titleRange.CharacterFormat.FontSize = 19;
    titleRange.CharacterFormat.Bold = true;
    titleRange.CharacterFormat.TextColor = wasmModule.Color.FromArgb(255, 15, 110, 90);
    titleParagraph.Format.AfterSpacing = 6;

    const subtitleParagraph = section.AddParagraph();
    const subtitleRange = subtitleParagraph.AppendText('Procurement Department    Q3 2026');
    subtitleRange.CharacterFormat.FontName = 'Arial';
    subtitleRange.CharacterFormat.FontSize = 9;
    subtitleRange.CharacterFormat.TextColor = wasmModule.Color.FromArgb(255, 89, 89, 89);
    subtitleParagraph.Format.AfterSpacing = 16;

    // Add the explanatory text before the control
    const paragraph = section.AddParagraph();
    paragraph.AppendText('Evaluation Result:  ');

    // Create an inline content control and add it to the paragraph
    const sdt = new wasmModule.StructureDocumentTagInline(doc);
    paragraph.ChildObjects.Add(sdt);

    // Set the control type to drop-down list
    sdt.SDTProperties.SDTType = wasmModule.SdtType.DropDownList;
    sdt.SDTProperties.Alias = 'Evaluation Result';
    sdt.SDTProperties.Tag = 'evaluation-result';
    sdt.SDTProperties.Appearance = wasmModule.SdtAppearance.Tags;

    // Build the drop-down list and add the options
    const list = new wasmModule.SdtDropDownList();
    list.ListItems.Add(new wasmModule.SdtListItem('Excellent'));
    list.ListItems.Add(new wasmModule.SdtListItem('Good'));
    list.ListItems.Add(new wasmModule.SdtListItem('Qualified'));
    list.ListItems.Add(new wasmModule.SdtListItem('Needs Improvement'));
    sdt.SDTProperties.ControlProperties = list;

    // Write the text of the currently selected option
    const content = new wasmModule.TextRange(doc);
    content.Text = list.ListItems.get_Item(1).DisplayText;
    sdt.SDTContent.ChildObjects.Add(content);

    // Add the closing note
    const noteParagraph = section.AddParagraph();
    noteParagraph.Format.BeforeSpacing = 10;
    const noteRange = noteParagraph.AppendText('A drop-down list only allows choices from the preset options, which suits fixed values such as conclusions and ratings.');
    noteRange.CharacterFormat.FontName = 'Arial';
    noteRange.CharacterFormat.FontSize = 9;
    noteRange.CharacterFormat.TextColor = wasmModule.Color.FromArgb(255, 89, 89, 89);

    // Save the result file
    const outputFileName = 'AddDropDownListContentControl.docx';
    doc.SaveToFile({ fileName: outputFileName, fileFormat: wasmModule.FileFormat.Docx2013 });
    doc.Dispose();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Add Drop-Down List Content Control</h1>
      <button onClick={addDropDownList}>Generate Document</button>
    </div>
  );
}

export default App;

Document after adding a drop-down list content control:

Document after adding a drop-down list content control

Display Text and Value of an Option

SdtListItem has two constructors:

Form Display text Internal value
new SdtListItem('Good') Good Good
new SdtListItem('Good', 'good') Good good

When only one argument is passed, the display text and the internal value are the same, which suits options that are themselves business terms. When two arguments are passed, the document displays the first one, while a program can read and compare the second one; setting the internal value to a stable English identifier keeps the meaning of the data unchanged even after the interface wording is adjusted.


Add a Combo Box Content Control

A combo box provides a list of candidates while allowing the person filling in the form to enter content outside the list. It suits fields such as product lines and supplier names that are "usually fixed, occasionally exceptional". The code structure is almost identical to that of the drop-down list; only the type changes to SdtType.ComboBox and the control property class to SdtComboBox.

The specific steps are as follows:

  1. Get the Spire.Doc WASM module and confirm that it is ready.
  2. Load the font, create a document with the Document class and add a section, then write the title and description.
  3. Create a StructureDocumentTagInline control in the paragraph and set SDTType to SdtType.ComboBox.
  4. Create an SdtComboBox, add the candidate options, assign it to ControlProperties, and write the text of the selected option.
  5. Save the result file with SaveToFile(), read it from the VFS and trigger a browser download.

The code for adding a combo box content control is as follows:

function App() {
  const addComboBox = async () => {
    // Get the Spire.Doc WASM module
    const wasmModule = window.wasmModule?.spiredoc;

    if (!wasmModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Load the font
    await window.spire.FetchFileToVFS('arial.ttf', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    await window.spire.FetchFileToVFS('arialbd.ttf', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);

    // Create a document and add a section
    const doc = new wasmModule.Document();
    const section = doc.AddSection();

    // Add the title and subtitle
    const titleParagraph = section.AddParagraph();
    const titleRange = titleParagraph.AppendText('Product Line Registration Form');
    titleRange.CharacterFormat.FontName = 'Arial';
    titleRange.CharacterFormat.FontSize = 19;
    titleRange.CharacterFormat.Bold = true;
    titleRange.CharacterFormat.TextColor = wasmModule.Color.FromArgb(255, 15, 110, 90);
    titleParagraph.Format.AfterSpacing = 6;

    const subtitleParagraph = section.AddParagraph();
    const subtitleRange = subtitleParagraph.AppendText('Marketing Department    November 2026');
    subtitleRange.CharacterFormat.FontName = 'Arial';
    subtitleRange.CharacterFormat.FontSize = 9;
    subtitleRange.CharacterFormat.TextColor = wasmModule.Color.FromArgb(255, 89, 89, 89);
    subtitleParagraph.Format.AfterSpacing = 16;

    // Add the explanatory text before the control
    const paragraph = section.AddParagraph();
    paragraph.AppendText('Partner Product Line:  ');

    // Create an inline content control and add it to the paragraph
    const sdt = new wasmModule.StructureDocumentTagInline(doc);
    paragraph.ChildObjects.Add(sdt);

    // Set the control type to combo box
    sdt.SDTProperties.SDTType = wasmModule.SdtType.ComboBox;
    sdt.SDTProperties.Alias = 'Partner Product Line';
    sdt.SDTProperties.Tag = 'product-line';
    sdt.SDTProperties.Appearance = wasmModule.SdtAppearance.Tags;

    // Build the combo box and add the candidates
    const combo = new wasmModule.SdtComboBox();
    combo.ListItems.Add(new wasmModule.SdtListItem('Office Suite'));
    combo.ListItems.Add(new wasmModule.SdtListItem('Document Services'));
    combo.ListItems.Add(new wasmModule.SdtListItem('Data Platform'));
    combo.ListItems.Add(new wasmModule.SdtListItem('Presentation Components'));
    sdt.SDTProperties.ControlProperties = combo;

    // Write the text of the currently selected option
    const content = new wasmModule.TextRange(doc);
    content.Text = combo.ListItems.get_Item(1).DisplayText;
    sdt.SDTContent.ChildObjects.Add(content);

    // Add the closing note
    const noteParagraph = section.AddParagraph();
    noteParagraph.Format.BeforeSpacing = 10;
    const noteRange = noteParagraph.AppendText('A combo box keeps the preset options available while allowing content outside the list.');
    noteRange.CharacterFormat.FontName = 'Arial';
    noteRange.CharacterFormat.FontSize = 9;
    noteRange.CharacterFormat.TextColor = wasmModule.Color.FromArgb(255, 89, 89, 89);

    // Save the result file
    const outputFileName = 'AddComboBoxContentControl.docx';
    doc.SaveToFile({ fileName: outputFileName, fileFormat: wasmModule.FileFormat.Docx2013 });
    doc.Dispose();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Add Combo Box Content Control</h1>
      <button onClick={addComboBox}>Generate Document</button>
    </div>
  );
}

export default App;

Document after adding a combo box content control:

Document after adding a combo box content control


Update Combo Box List Items and Selected Value

In practice, options change over time: a product line is retired and has to be removed from the list; a new product line has to be added; and for some forms a recommended value has to be selected in advance. All of these operations can be performed on a loaded document without regenerating the whole file.

To update the control, first iterate over the paragraphs and tables in the body, find the control whose DocumentObjectType is StructureDocumentTagInline and whose type is ComboBox, then call the add and remove methods of ListItems on its ControlProperties. The example reads ComboBox.docx from public/data, so put that file in the project's public/data directory before running it.

Member Purpose
ListItems.RemoveAt(index) Removes the option at the specified index
ListItems.Add(item) Appends a new option
ListItems.Count Current number of options
ListItems.SelectedValue Selected value; must be assigned an SdtListItem object

The specific steps are as follows:

  1. Get the Spire.Doc WASM module and confirm that it is ready.
  2. Load the font, read ComboBox.docx into the VFS and load it as a Document.
  3. Iterate over the body objects of each section and the child objects of each paragraph, and filter out the combo box content controls.
  4. Remove the old option with RemoveAt, append a new one with Add, then compare by Value and set SelectedValue.
  5. Save the result file with SaveToFile(), read it from the VFS and trigger a browser download.

The code for updating the combo box list items and selected value is as follows:

function App() {
  const updateComboBoxItems = async () => {
    // Get the Spire.Doc WASM module
    const wasmModule = window.wasmModule?.spiredoc;

    if (!wasmModule) {
      alert('Spire.Doc is not ready yet');
      return;
    }

    // Load the document to be processed
    await window.spire.FetchFileToVFS('ComboBox.docx', '', `${process.env.PUBLIC_URL}/data/`);

    const doc = new wasmModule.Document();
    doc.LoadFromFile('ComboBox.docx');

    // Iterate over the sections and find combo box content controls
    for (let s = 0; s < doc.Sections.Count; s++) {
      const section = doc.Sections.get_Item(s);
      for (let i = 0; i < section.Body.ChildObjects.Count; i++) {
        const obj = section.Body.ChildObjects.get_Item(i);
        if (obj.DocumentObjectType !== wasmModule.DocumentObjectType.Paragraph) continue;

        for (let j = 0; j < obj.ChildObjects.Count; j++) {
          const child = obj.ChildObjects.get_Item(j);
          if (child.DocumentObjectType !== wasmModule.DocumentObjectType.StructureDocumentTagInline) continue;
          if (child.SDTProperties.SDTType !== wasmModule.SdtType.ComboBox) continue;

          const combo = child.SDTProperties.ControlProperties;

          // Remove the old option at index 1 and append a new one
          combo.ListItems.RemoveAt(1);
          combo.ListItems.Add(new wasmModule.SdtListItem('Smart Documents', 'Smart Documents'));

          // Find the new option by Value and set it as the selected value
          for (let k = 0; k < combo.ListItems.Count; k++) {
            const item = combo.ListItems.get_Item(k);
            if (item.Value === 'Smart Documents') {
              combo.ListItems.SelectedValue = item;
            }
          }
        }
      }
    }

    // Save the result file
    const outputFileName = 'UpdateComboBoxItems.docx';
    doc.SaveToFile({ fileName: outputFileName, fileFormat: wasmModule.FileFormat.Docx2013 });
    doc.Dispose();

    // Read the generated file from the VFS and trigger the download
    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = outputFileName;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div style={{ textAlign: 'center', height: '300px' }}>
      <h1>Update Combo Box List Items and Selected Value</h1>
      <button onClick={updateComboBoxItems}>Generate Document</button>
    </div>
  );
}

export default App;

The comparison before and after processing is shown below; the left image is the original document and the right image is the document after updating:

Comparison before and after updating the combo box list items and selected value

Traversal Order When Locating the Control

The example scans only the inline controls in paragraphs directly under the body, which is the most common location. If a control is inside a table cell, first check whether the object is a Table, then walk row by row and cell by cell into Cell.ChildObjects to find it; if the control is a block-level StructureDocumentTag, compare it directly at the Body.ChildObjects level. When traversing, check DocumentObjectType before reading properties so that ordinary paragraphs are not treated as controls by mistake.


FAQ

How to Choose Between a Drop-Down List and a Combo Box

Cause: The two controls differ mainly in whether values outside the preset options are allowed, and this has to be decided together with the business rules for the field.

Solution: If the field's values must be strictly limited to the preset range, use a drop-down list; if the list only provides common candidates and the business allows content outside it, use a combo box. A drop-down list is better for fields such as ratings, statuses and categories that need a consistent vocabulary, while a combo box is better for fields such as names and product lines that may expand with the business.

Why the Text Displayed by the Control Does Not Change After Setting SelectedValue

Cause: SelectedValue must be assigned an SdtListItem object; assigning a string directly has no effect.

Solution: In the example, iterate over ListItems, compare Value to find the target item, then assign that item object to SelectedValue. After the change, save the file and Word displays the selected item.

Why the Wrong Item Is Selected After RemoveAt

Cause: After RemoveAt deletes an element, the indexes of the remaining options shift forward. If you delete first and then access by the original index, you may get a different option.

Solution: After deleting, iterate over ListItems again and find the target item by Value or DisplayText, rather than reusing the index from before the deletion.

How to Select an Option by the Text the User Sees

Cause: Value and DisplayText can be different, so which one to compare against depends on what the business treats as authoritative.

Solution: If the interface text is authoritative, change the comparison condition to item.DisplayText === 'Smart Documents'; if a stable internal identifier is authoritative, keep comparing Value. Do not mix the two, or selection may fail after the wording is adjusted.


Get a Free License

Spire.Doc for JavaScript offers a 30-day full-featured free trial license with no functional limitations. Apply here to evaluate before purchasing.