Create Lists in PDF Documents Using JavaScript in React

Product catalogs, checklists, step-by-step instructions, and clause summaries all read most clearly in a PDF when they are laid out as lists. Typing them out one by one in an editor is workable, but once the entries have to follow the data — exporting a category table or a to-do list to PDF, for example — manual layout stops keeping up: the number of entries, the numbering order, and the indentation levels all have to come from the data.

This article shows how to use Spire.PDF for JavaScript to create unordered lists, ordered lists, and multilevel lists, where unordered lists come in two forms: built-in markers and image markers. It runs on WebAssembly to create and save PDF documents directly in the browser, doing all the work locally and reading and writing files through a virtual file system (VFS) with no backend involved.

This article covers four core features:

Different list types use different classes and marker objects. Pick from the table below:

List type List class Marker configuration Common values
Unordered list (built-in markers) PdfList PdfMarker + PdfUnorderedMarkerStyle Disk, Square, Circle, Asterisk
Unordered list (image markers) PdfList the image parameter of PdfMarker (the style becomes CustomImage automatically) Any image, scaled to the text line height
Ordered list (numbers) PdfSortedList PdfOrderedMarker + PdfNumberStyle (Suffix changes the number suffix, StartNumber changes the starting number) Numeric, LowerLatin, UpperLatin, LowerRoman, UpperRoman
Multilevel list (nested) PdfList / PdfSortedList + the list item's SubList each level's own Marker and Indent the top level and the sublevel can each use a different marker or number style

For installation and project configuration, see Integrate Spire.PDF for JavaScript into a React Project. The examples below assume Spire.PDF is installed and the WebAssembly module is initialized.


Create an Unordered List in a PDF Page

Spire.PDF for JavaScript provides PdfList to create lists, switching the marker shape through the PdfUnorderedMarkerStyle enumeration.

function App() {
  const createBulletLists = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check whether the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load the font into the VFS for the list text
    await window.spire.FetchFileToVFS('ARIAL UNICODE MS.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);

    // Create a PDF document and add a blank page
    const doc = new pdfModule.PdfDocument();
    const page = doc.Pages.Add();

    // Font and item content for the list
    const font = new pdfModule.PdfTrueTypeFont({ fontFile: '/Library/Fonts/ARIAL UNICODE MS.TTF', size: 12 });
    const items = ['Fruit Juice', 'Condiments', 'Confectionery', 'Dairy Products', 'Grains & Cereals', 'Meat & Poultry', 'Fruits & Vegetables', 'Seafood'];

    // First list: add items one by one, with Square markers
    const list = new pdfModule.PdfList({ font: font });
    for (const item of items) {
      list.Items.Add(item);
    }
    list.Marker = new pdfModule.PdfMarker({ style: pdfModule.PdfUnorderedMarkerStyle.Square });
    // Brush affects both the marker and the list text
    list.Brush = new pdfModule.PdfSolidBrush({ pdfRGBColor: new pdfModule.PdfRGBColor({ color: pdfModule.Color.get_Navy() }) });
    list.Indent = 10;
    list.TextIndent = 6;
    const first = list.Draw({ page: page, x: 0, y: 40 });

    // Second list drawn below the first one, with Circle markers
    const list2 = new pdfModule.PdfList({ font: font });
    for (const item of items) {
      list2.Items.Add(item);
    }
    list2.Marker = new pdfModule.PdfMarker({ style: pdfModule.PdfUnorderedMarkerStyle.Circle });
    list2.Brush = pdfModule.PdfBrushes.get_Black();
    list2.Indent = 10;
    list2.TextIndent = 6;
    list2.Draw({ page: page, x: 0, y: first.Bounds.Bottom + 20 });

    // Save and read back from the VFS to trigger the download
    const outputFileName = 'BulletList.pdf';
    doc.SaveToFile(outputFileName);
    doc.Close();

    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    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>Create an Unordered List</h1>
      <button onClick={createBulletLists}>
        Create
      </button>
    </div>
  );
}

export default App;

Two unordered lists of the same content, using Square and Circle markers

Two unordered lists of the same content, using Square and Circle markers


Create an Unordered List with Images in a PDF Page

Spire.PDF for JavaScript also provides PdfImage to read an image; hand it to PdfMarker and it becomes the bullet marker, with the style switching to CustomImage automatically.

function App() {
  const createImageBulletList = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check whether the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load the font and the bullet image into the VFS
    await window.spire.FetchFileToVFS('ARIAL UNICODE MS.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
    await window.spire.FetchFileToVFS('logo.png', '', `${process.env.PUBLIC_URL}/data/`);

    // Create a PDF document and add a blank page
    const doc = new pdfModule.PdfDocument();
    const page = doc.Pages.Add();

    // Font and item content for the list
    const font = new pdfModule.PdfTrueTypeFont({ fontFile: '/Library/Fonts/ARIAL UNICODE MS.TTF', size: 12 });
    const items = ['Fruit Juice', 'Condiments', 'Confectionery', 'Dairy Products', 'Grains & Cereals', 'Meat & Poultry', 'Fruits & Vegetables', 'Seafood'];

    // Read the image and hand it to PdfMarker as the marker; the style becomes CustomImage automatically
    const image = pdfModule.PdfImage.FromFile('logo.png');
    const marker = new pdfModule.PdfMarker({ image: image });

    // Add items one by one and apply the image marker
    const list = new pdfModule.PdfList({ font: font });
    for (const item of items) {
      list.Items.Add(item);
    }
    list.Marker = marker;
    list.Brush = pdfModule.PdfBrushes.get_Black();
    list.Indent = 10;
    list.TextIndent = 6;
    list.Draw({ page: page, x: 0, y: 40 });

    // Save and read back from the VFS to trigger the download
    const outputFileName = 'ImageBulletList.pdf';
    doc.SaveToFile(outputFileName);
    doc.Close();

    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    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>Create an Unordered List with Images</h1>
      <button onClick={createImageBulletList}>
        Create
      </button>
    </div>
  );
}

export default App;

An unordered list using logo.png as the bullet marker

An unordered list using logo.png as the bullet marker


Create an Ordered List in a PDF Page

An ordered list is created by PdfSortedList, its numbers increment automatically with the items, and the number format is decided by the PdfNumberStyle enumeration on PdfOrderedMarker.

function App() {
  const createOrderedLists = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check whether the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load the font into the VFS for the list text
    await window.spire.FetchFileToVFS('ARIAL UNICODE MS.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);

    // Create a PDF document and add a blank page
    const doc = new pdfModule.PdfDocument();
    const page = doc.Pages.Add();

    // Font and item content for the list
    const font = new pdfModule.PdfTrueTypeFont({ fontFile: '/Library/Fonts/ARIAL UNICODE MS.TTF', size: 12 });
    const items = ['Fruit Juice', 'Condiments', 'Confectionery', 'Dairy Products'];

    // First list: Arabic numerals, with "." as the default number suffix
    const list = new pdfModule.PdfSortedList({
      marker: new pdfModule.PdfOrderedMarker({ style: pdfModule.PdfNumberStyle.Numeric, font: font }),
    });
    list.Font = font;
    for (const item of items) {
      list.Items.Add(item);
    }
    list.Indent = 12;
    list.TextIndent = 6;
    list.Brush = pdfModule.PdfBrushes.get_Black();
    const first = list.Draw({ page: page, x: 0, y: 40 });

    // Second list: uppercase Roman numerals, with the suffix changed to ")"
    const marker = new pdfModule.PdfOrderedMarker({ style: pdfModule.PdfNumberStyle.UpperRoman, font: font });
    marker.Suffix = ')';
    const list2 = new pdfModule.PdfSortedList({ marker: marker });
    list2.Font = font;
    for (const item of items) {
      list2.Items.Add(item);
    }
    list2.Indent = 12;
    list2.TextIndent = 6;
    list2.Brush = pdfModule.PdfBrushes.get_Black();
    list2.Draw({ page: page, x: 0, y: first.Bounds.Bottom + 20 });

    // Save and read back from the VFS to trigger the download
    const outputFileName = 'NumberedList.pdf';
    doc.SaveToFile(outputFileName);
    doc.Close();

    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    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>Create an Ordered List</h1>
      <button onClick={createOrderedLists}>
        Create
      </button>
    </div>
  );
}

export default App;

Ordered lists created with Arabic numerals and uppercase Roman numerals

Ordered lists created with Arabic numerals and uppercase Roman numerals


Create a Multilevel List in a PDF Page

A multilevel list is built on the SubList property of an item: attach a sublist to an item and it gains one more level, with the indentation decided by the sublist's own Indent.

function App() {
  const createMultilevelList = async () => {
    // Get the Spire.PDF WASM module
    const pdfModule = window.wasmModule?.spirepdf;

    // Check whether the module is ready
    if (!pdfModule) {
      alert('Spire.PDF is not ready yet');
      return;
    }

    // Load the font into the VFS for the list text
    await window.spire.FetchFileToVFS('ARIAL UNICODE MS.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);

    // Create a PDF document and add a blank page
    const doc = new pdfModule.PdfDocument();
    const page = doc.Pages.Add();

    // Font and grouped data for the list
    const font = new pdfModule.PdfTrueTypeFont({ fontFile: '/Library/Fonts/ARIAL UNICODE MS.TTF', size: 12 });
    const groups = [
      { title: 'Beverages & Dairy', children: ['Fruit Juice', 'Dairy Products'] },
      { title: 'Fresh Produce & Grains', children: ['Grains & Cereals', 'Fruits & Vegetables'] },
      { title: 'Meat & Seafood', children: ['Meat & Poultry', 'Seafood'] },
    ];

    // Top-level list: Arabic numerals
    const root = new pdfModule.PdfSortedList({
      marker: new pdfModule.PdfOrderedMarker({ style: pdfModule.PdfNumberStyle.Numeric, font: font }),
    });
    root.Font = font;
    root.Indent = 10;
    root.TextIndent = 6;
    root.Brush = pdfModule.PdfBrushes.get_Black();

    // Attach a sublist to each top-level item, with Disk markers indented by 18 points
    for (const group of groups) {
      const item = root.Items.Add(group.title);
      const sub = new pdfModule.PdfList({ font: font });
      for (const child of group.children) {
        sub.Items.Add(child);
      }
      sub.Marker = new pdfModule.PdfMarker({ style: pdfModule.PdfUnorderedMarkerStyle.Disk });
      sub.Indent = 18;
      sub.TextIndent = 6;
      sub.Brush = pdfModule.PdfBrushes.get_Black();
      item.SubList = sub;
    }

    // Draw the whole multilevel list starting from the top of the page
    root.Draw({ page: page, x: 0, y: 40 });

    // Save and read back from the VFS to trigger the download
    const outputFileName = 'MultilevelList.pdf';
    doc.SaveToFile(outputFileName);
    doc.Close();

    const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
    const blob = new Blob([fileArray], { type: 'application/pdf' });
    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>Create a Multilevel List</h1>
      <button onClick={createMultilevelList}>
        Create
      </button>
    </div>
  );
}

export default App;

A two-level list made of numbered top-level items and bulleted sublists

A two-level list made of numbered top-level items and bulleted sublists


FAQ

The list item text does not show up

Cause: List items use the font on the Font property. If it is not set, or the font file has not been loaded into the virtual file system yet, only the bullet markers are left on the page and the text never appears.

Solution: Load the font file into /Library/Fonts/ first, then hand the font object to the list's Font:

// Load the font file into the virtual file system
await window.spire.FetchFileToVFS('ARIAL UNICODE MS.TTF', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);

// Create the font from that file and pass it in when constructing the list (assigning it to list.Font afterwards works the same way)
const font = new pdfModule.PdfTrueTypeFont({ fontFile: '/Library/Fonts/ARIAL UNICODE MS.TTF', size: 12 });
const list = new pdfModule.PdfList({ font: font });

"Ambiguous call" is thrown when creating a list

Cause: PdfList, PdfSortedList, and PdfMarker all have multiple overloads, so positional arguments leave the runtime unable to tell which one to use; it throws Ambiguous call: arguments (object) match multiple overloads. and lists the available key names in the error. Positional arguments in list.Draw(page, x, y) are rejected the same way.

Solution: Switch to object notation and spell out the parameter names:

// This throws: new pdfModule.PdfList(font)
const list = new pdfModule.PdfList({ font: font });
const sortedList = new pdfModule.PdfSortedList({ marker: marker });
const marker2 = new pdfModule.PdfMarker({ style: pdfModule.PdfUnorderedMarkerStyle.Disk });

// Drawing works the same way: list.Draw(page, 0, 40) throws, so write
list.Draw({ page: page, x: 0, y: 40 });

"IO_FileNotFound_FileName" is thrown when using an image as the bullet marker

Cause: PdfImage.FromFile reads a path inside the virtual file system, so it throws IO_FileNotFound_FileName when the image has not been loaded with FetchFileToVFS first — having the image on disk does not count.

Solution: Load the image into the virtual file system first, then read it with the same file name:

// Load the image into the virtual file system
await window.spire.FetchFileToVFS('logo.png', '', `${process.env.PUBLIC_URL}/data/`);

// Read it with the same file name
const image = pdfModule.PdfImage.FromFile('logo.png');
const marker = new pdfModule.PdfMarker({ image: image });

Get a Free License

If you want to remove the evaluation message from the resulting documents, or to get rid of the feature limitations, contact sales for a temporary license valid for 30 days.