Skip to content

Tabs

Give your fields a tab and the dialog gets a tab bar. There is nothing else to configure: the tabs come from the fields, and are not declared separately.

Tabs are only rendered in a dialog, so this demo opens one. Note that the third tab appears only after you check the box: a tab whose fields are all inactive is not shown.

ts
{
  fields: [
    { name: 'title', displayName: 'Title', tab: 'General', defaultValue: 'Report' },
    { name: 'author', displayName: 'Author', tab: 'General' },
    {
      name: 'expert',
      kind: 'checkbox',
      label: 'Show advanced options',
      tab: 'General',
    },
    {
      name: 'template',
      kind: 'select',
      displayName: 'Template',
      options: ['Plain', 'Academic'],
      tab: 'Export',
    },
    {
      name: 'apiKey',
      displayName: 'API key',
      tab: 'Advanced',
      isActive: ({ data }) => data['expert'] === true,
    },
  ],
}
Opens a dialog

Values

Ordering

Tabs appear in the order in which the fields are declared. The first field that names a tab decides that tab's position. If you reorder the fields, the tab bar changes with them.

Empty tabs disappear

A tab is only rendered when at least one of its fields is currently active. This is the main reason why tabs come from the fields instead of being declared: a conditional section disappears from the tab bar, instead of opening an empty page.

ts
{
  name: 'apiKey',
  tab: 'Advanced',
  isActive: ({ data }) => data['mode'] === 'expert',
}
// The "Advanced" tab only exists while mode === 'expert'.

If the tab that is currently open disappears, the form switches to the first tab that is left.

A field in several tabs

tab accepts an array:

ts
{ name: 'note', displayName: 'Note', tab: ['General', 'Export'] }

The field is then shown in both tabs.

Computed tabs

tab is reactive, so a field can move from one tab to another:

ts
{
  name: 'preview',
  tab: ({ data }) => (data['mode'] === 'edit' ? 'Edit' : 'Preview'),
}

A fixed tab name also becomes a CSS class

If tab is a fixed string, its name — with the spaces removed — is added to the field wrapper as a class. So tab: 'Reference Sources' gives you .ReferenceSources. The class is written when the form is built, so a tab that is calculated by a function cannot add one. See the DOM contract.

Fields with no tab

A field without a tab is shown in every tab. This is the right choice for a title field, or for a summary message that should stay visible while the user moves between tabs.

Reading and setting the active tab

The active tab is always part of the form values:

ts
form.getValues()['activeTab']; // 'General'

Switch programmatically:

ts
form.setActiveTab('Export');

Called without an argument, setActiveTab() applies the current tab again. This is useful to bring the form back in sync after you have changed something behind its back:

ts
form.setActiveTab();

Selecting a tab that is not currently rendered does nothing, and is not an error, so an old tab name cannot break the dialog.

Wizards

The tab buttons are ordinary sibling elements inside .tabWrapper, so a Back/Next wizard takes only a few lines. This is the pattern used in examples/main.ts:

ts
const modal = form.openInModal();

function step(delta: number): void {
  const tabs = [...modal.querySelectorAll('.dl-tab-btn')];
  const index = tabs.findIndex((t) => t.classList.contains('active'));
  (tabs[index + delta] as HTMLElement | undefined)?.click();
}

Then connect it to buttons that do not close the dialog:

ts
buttons: {
  Back: { id: 'backBtn', class: 'secondary', doNotCloseModal: true, action: () => step(-1) },
  Next: { id: 'nextBtn', doNotCloseModal: true, action: () => step(1) },
  Export: { id: 'exportBtn', isActive: async ({ data }) => isComplete(data), action: save },
}

See Buttons.

Styling

Every tab button has the class .dl-tab-btn. The current one also has .active, and every tab the user has visited keeps .seen, which is useful for showing progress in a wizard.

css
.dl-tab-btn.seen:not(.active) {
  opacity: 0.7;
}

Released under the MIT License.