Components

Things to build with. Each has a main class, classes for its parts, and classes for its variations. They draw everything from the tokens, so they follow the theme and the Appearance page, including the look applied to the site.

Button

An action: a <button>, an <a> or an <input type="submit">. A secondary button by default.

ClassWhat it does
fz-btnA secondary button.
fz-btn--primaryThe main action. Usually one per area.
fz-btn--dangerA destructive action, such as delete.
fz-btn--ghostNo border or background until hovered, for less important actions.
fz-btn--{sm,lg}Smaller or larger.
fz-btn--iconSquare, for an icon on its own. Give it an aria-label.
fz-btn--blockThe full width of its container.
  • Use a <button> for an action on the page and an <a> for going somewhere else; they look the same.
  • A disabled button, or one with aria-disabled="true", is faded.
Example
<div class="fz-cluster">
  <button type="button" class="fz-btn fz-btn--primary">Save</button>
  <button type="button" class="fz-btn">Cancel</button>
  <a href="#" class="fz-btn fz-btn--ghost">View history</a>
  <button type="button" class="fz-btn fz-btn--danger">Delete</button>
  <button type="button" class="fz-btn fz-btn--sm">Small</button>
  <button type="button" class="fz-btn fz-btn--lg">Large</button>
  <button type="button" class="fz-btn fz-btn--icon" aria-label="Settings">&#9881;</button>
  <button type="button" class="fz-btn" disabled>Disabled</button>
</div>

Form field

A label, a control and its help text.

ClassWhat it does
fz-fieldThe field: its label above its control.
fz-field__labelThe label: a <label for="…">, or a <legend> on a <fieldset>.
fz-field__reqThe required marker, inside the label.
fz-field__bodyThe control and its help text.
fz-field__helpHelp text, or an error message.
fz-field--horizontalThe label beside the control, stacking when there isn't room.
  • For a group of choices, use fz-field on a <fieldset>, with a <legend class="fz-field__label">.
  • Connect help text to its control with aria-describedby, so screen readers read it too.
Example
<div class="fz-stack">
  <div class="fz-field">
    <label class="fz-field__label" for="sg-name">Student name <span class="fz-field__req" aria-hidden="true">*</span></label>
    <div class="fz-field__body">
      <input class="fz-input" id="sg-name" required aria-describedby="sg-name-help">
      <p class="fz-field__help" id="sg-name-help">As it appears on the student's report card.</p>
    </div>
  </div>
  <div class="fz-field fz-field--horizontal">
    <label class="fz-field__label" for="sg-school">School</label>
    <div class="fz-field__body"><input class="fz-input" id="sg-school"></div>
  </div>
</div>

Inputs, dropdowns and text areas

Text-like inputs, dropdowns and text areas, sharing one look. Their height is the field height, which the site's look sets.

ClassWhat it does
fz-inputAn <input>: text, email, number, date and the like.
fz-selectA <select>, with its own chevron.
fz-textareaA <textarea>, several lines, taller when dragged.
  • aria-invalid="true" marks a control as invalid. Say what is wrong in the field's help text too, since the colour alone doesn't.
Example
<div class="fz-auto-grid fz-auto-grid--max-2 fz-auto-grid--wide">
  <input class="fz-input" placeholder="A text input" aria-label="A text input">
  <select class="fz-select" aria-label="A dropdown"><option>Grade 6</option><option>Grade 7</option></select>
  <input class="fz-input" value="not-an-email" aria-invalid="true" aria-label="An invalid input">
  <input class="fz-input" value="Disabled" disabled aria-label="A disabled input">
  <textarea class="fz-textarea" placeholder="A text area" aria-label="A text area"></textarea>
</div>

Input group

An input with text or an icon attached before or after it, such as a currency or a unit.

ClassWhat it does
fz-input-groupWraps an fz-input and its add-ons.
fz-input-group__addonThe attached text or icon.
Example
<div class="fz-input-group" style="max-width: 20rem">
  <span class="fz-input-group__addon">$</span>
  <input class="fz-input" inputmode="decimal" value="120.00" aria-label="Program fee">
  <span class="fz-input-group__addon">CAD</span>
</div>

Checkboxes, radios and toggles

Real checkboxes and radio buttons, drawn in the theme's colours; a toggle is a checkbox drawn as an on/off switch.

ClassWhat it does
fz-checkboxAn <input type="checkbox">.
fz-radioAn <input type="radio">.
fz-toggleAn <input type="checkbox"> drawn as a switch.
fz-choiceA <label> around a checkbox, radio or toggle and its text, all of it clickable.
fz-choice-groupA set of choices, one below the other.
Example
<div class="fz-switcher">
  <fieldset class="fz-field">
    <legend class="fz-field__label">Transportation</legend>
    <div class="fz-choice-group">
      <label class="fz-choice"><input type="radio" class="fz-radio" name="sg-t" checked> Bus</label>
      <label class="fz-choice"><input type="radio" class="fz-radio" name="sg-t"> Picked up by a parent</label>
    </div>
  </fieldset>
  <div class="fz-choice-group">
    <label class="fz-choice"><input type="checkbox" class="fz-checkbox" checked> Photo consent</label>
    <label class="fz-choice"><input type="checkbox" class="fz-toggle" checked> Email updates</label>
  </div>
</div>

Card

A surface for one thing: an entry, a summary, a group of fields. Its children are stacked 3 steps apart.

ClassWhat it does
fz-cardThe card.
fz-card__headerA row for the title and any actions, wrapping when out of room.
fz-card__titleThe title, on a heading that fits the page (h2, h3…).
fz-card__subtitleA line of secondary text under the title.
fz-card__footerA row at the bottom, separated by a line.
fz-card--flushNo padding: for a table or an image that should reach the edges.
fz-card--interactiveThe whole card is clickable, through its fz-card__link.
fz-card__linkThe one link the whole card clicks through to: on the link, or on an element around it when the link is generated for you, such as by viewEntryLink() in a list template.
  • In an interactive card, other links and buttons still work on their own.
Example
<div class="fz-auto-grid fz-auto-grid--max-2 fz-auto-grid--wide">
  <article class="fz-card">
    <div class="fz-card__header">
      <div><h3 class="fz-card__title">Robotics club</h3><p class="fz-card__subtitle">Thursdays, 3:30 to 5pm</p></div>
      <span class="fz-badge fz-badge--success">Open</span>
    </div>
    <p>An after-school program for students in grades 6 to 8.</p>
    <div class="fz-card__footer"><span class="fz-text-muted fz-text-sm">18 of 24 places taken</span><button type="button" class="fz-btn fz-btn--sm fz-ms-auto">Register</button></div>
  </article>
  <article class="fz-card fz-card--interactive">
    <h3 class="fz-card__title"><a href="#" class="fz-card__link">Coding camp</a></h3>
    <p class="fz-text-muted">The whole card is a link.</p>
    <div class="fz-cluster"><span class="fz-badge fz-badge--warning">Waitlist</span></div>
  </article>
</div>

Card list and grid list

A list of entries shown as cards instead of table rows: one below the other, or in columns.

ClassWhat it does
fz-card-listOn a <ul> or <ol>: cards one below the other.
fz-grid-listOn a <ul> or <ol>: cards in columns, like fz-auto-grid.
fz-grid-list--max-{2-6}At most this many columns.
fz-grid-list--{narrow,wide}Columns as narrow as 8rem, or at least 18rem.
  • Give each <li> the fz-card class.
Example
<ul class="fz-grid-list fz-grid-list--max-3">
  <li class="fz-card fz-card--interactive"><h3 class="fz-card__title fz-card__link"><a href="#">Amira Haddad</a></h3><div class="fz-cluster"><span class="fz-badge fz-badge--success">Approved</span><span class="fz-badge">Grade 7</span></div></li>
  <li class="fz-card fz-card--interactive"><h3 class="fz-card__title fz-card__link"><a href="#">Liam Chen</a></h3><div class="fz-cluster"><span class="fz-badge fz-badge--warning">Under review</span><span class="fz-badge">Grade 8</span></div></li>
  <li class="fz-card fz-card--interactive"><h3 class="fz-card__title fz-card__link"><a href="#">Sofia Rossi</a></h3><div class="fz-cluster"><span class="fz-badge fz-badge--info">Submitted</span><span class="fz-badge">Grade 6</span></div></li>
</ul>

Table

A plain data table: a header row, a line under each row, and rows the row height, which the site's look sets.

ClassWhat it does
fz-tableOn a <table>.
fz-table--stripedEvery other row shaded.
fz-table--multilineFor rows whose cells hold more than a line, such as a title over a description: the cells line up at the top, with space above and below.
  • For a table wider than its container, wrap it in an element with fz-overflow-x-auto.
Example
<div class="fz-card fz-card--flush fz-overflow-x-auto">
  <table class="fz-table">
    <thead><tr><th>Student</th><th>School</th><th class="fz-text-end">Grade</th><th>Status</th></tr></thead>
    <tbody>
      <tr><td>Amira Haddad</td><td>Riverside Public School</td><td class="fz-text-end fz-tabular-nums">7</td><td><span class="fz-badge fz-badge--success">Approved</span></td></tr>
      <tr><td>Liam Chen</td><td>Lakeview Middle School</td><td class="fz-text-end fz-tabular-nums">8</td><td><span class="fz-badge fz-badge--warning">Under review</span></td></tr>
    </tbody>
  </table>
</div>

Badge

A short label: a status, a category, a count.

ClassWhat it does
fz-badgeA neutral badge, with a grey dot.
fz-badge--{accent,info,success,warning,danger}A coloured tint and dot.
fz-badge--plainNo dot.
fz-badge--iconA circle, for an icon on its own. Every one is the same size, so a column of them lines up. No dot.
  • The colour is in the tint and the dot; the text stays the normal text colour, so it is readable in every theme. The word should say what the status is: don't rely on the colour alone.
  • An icon on its own says nothing to someone who cannot see it, or does not know it. Give an icon badge a title, and the same words inside it in an element with fz-sr-only.
Example
<div class="fz-cluster">
  <span class="fz-badge">Draft</span>
  <span class="fz-badge fz-badge--info">Submitted</span>
  <span class="fz-badge fz-badge--warning">Under review</span>
  <span class="fz-badge fz-badge--success">Approved</span>
  <span class="fz-badge fz-badge--danger">Rejected</span>
  <span class="fz-badge fz-badge--accent fz-badge--plain">12 new</span>
  <span class="fz-badge fz-badge--icon fz-badge--warning" title="Featured">&#9733;<span class="fz-sr-only">Featured</span></span>
</div>

Callout

A message set apart from the content around it: instructions, a warning, the result of an action.

ClassWhat it does
fz-calloutA callout, in the accent colour.
fz-callout--{info,success,warning,danger}A status callout.
fz-callout__titleA bold first line.
  • Add role="status" to a callout that appears because of something the user just did, so screen readers announce it.
Example
<div class="fz-stack fz-gap-3">
  <div class="fz-callout"><p class="fz-callout__title">Before you start</p><p>Have your student number and your school's address ready.</p></div>
  <div class="fz-callout fz-callout--success" role="status"><p>Your application was submitted.</p></div>
  <div class="fz-callout fz-callout--warning"><p>This entry has not been reviewed yet.</p></div>
  <div class="fz-callout fz-callout--danger"><p>The due date has passed.</p></div>
</div>

Toolbar

A row with a group at each end, such as a heading and its buttons, wrapping when it runs out of room.

ClassWhat it does
fz-toolbarThe row.
fz-toolbar__startThe group at the start.
fz-toolbar__endThe group at the end.
Example
<div class="fz-toolbar">
  <div class="fz-toolbar__start"><h2 class="fz-text-xl fz-font-semibold">Applications</h2><span class="fz-badge fz-badge--plain">48</span></div>
  <div class="fz-toolbar__end"><button type="button" class="fz-btn">Export</button><button type="button" class="fz-btn fz-btn--primary">New application</button></div>
</div>

Description list

Labelled values, such as an entry's fields in a card. On a <dl>; each <dt> and <dd> pair may be wrapped in a <div>.

ClassWhat it does
fz-dlTerms and values side by side.
fz-dl--stackedEach term above its value.
  • In a derived value, put the class on a <div> around the <dl> instead: <div class="fz-dl"><dl>...</dl></div>. A derived value's HTML is filtered before it is shown, and the filter takes the class off a <dl> but leaves it on a <div>.
Example
<div class="fz-switcher">
  <dl class="fz-dl">
    <dt>Student</dt><dd>Amira Haddad</dd>
    <dt>School</dt><dd>Riverside Public School</dd>
    <dt>Grade</dt><dd>7</dd>
  </dl>
  <dl class="fz-dl fz-dl--stacked">
    <div><dt>Submitted</dt><dd>September 12, 2026</dd></div>
    <div><dt>Status</dt><dd><span class="fz-badge fz-badge--success">Approved</span></dd></div>
  </dl>
</div>

Empty state

What to show where there is nothing to show yet, and what to do about it.

ClassWhat it does
fz-emptyA centred, outlined area.
fz-empty__titleIts heading.
Example
<div class="fz-empty">
  <p class="fz-empty__title">No applications yet</p>
  <p>They will appear here once students start submitting them.</p>
  <button type="button" class="fz-btn fz-btn--primary">Share the form</button>
</div>

Bar list

A few things compared by a number: for each, a label, a bar as long as its share, and the figure. For a breakdown by status or by category on a dashboard.

ClassWhat it does
fz-bar-listThe list.
fz-bar-list--two-valuesEach row has two figures after its bar, such as a count and a percentage.
fz-bar-list__rowOne thing: its label, its bar and its figure. The bar is the accent colour.
fz-bar-list__row--{info,success,warning,danger,neutral}The bar in a status colour, or grey.
fz-bar-list__labelWhat the row is. It can hold an icon or a badge as well as text.
fz-bar-list__trackThe full width a bar could be.
fz-bar-list__barThe bar. Give it its width as a percentage, in a style attribute.
fz-bar-list__valueA figure.
  • It is made of <div> and <span> elements, in this order in each row: label, track with the bar in it, then the figures.
  • The figure says what the bar shows, so the bar itself needs no text of its own.
  • For a colour that is not one of the status colours, such as the steps of a scale, set --fz-tone on the row.
  • Work out the percentages in your code. The bar's width is the share of the whole; use the share of the largest one instead to compare the rows with each other.
Example
<div class="fz-bar-list fz-bar-list--two-values">
  <div class="fz-bar-list__row fz-bar-list__row--success">
    <span class="fz-bar-list__label">Approved</span>
    <span class="fz-bar-list__track"><span class="fz-bar-list__bar" style="width: 52%"></span></span>
    <span class="fz-bar-list__value">31</span>
    <span class="fz-bar-list__value fz-font-normal fz-text-muted">52%</span>
  </div>
  <div class="fz-bar-list__row fz-bar-list__row--warning">
    <span class="fz-bar-list__label">Under review</span>
    <span class="fz-bar-list__track"><span class="fz-bar-list__bar" style="width: 20%"></span></span>
    <span class="fz-bar-list__value">12</span>
    <span class="fz-bar-list__value fz-font-normal fz-text-muted">20%</span>
  </div>
  <div class="fz-bar-list__row fz-bar-list__row--danger">
    <span class="fz-bar-list__label">Rejected</span>
    <span class="fz-bar-list__track"><span class="fz-bar-list__bar" style="width: 8%"></span></span>
    <span class="fz-bar-list__value">5</span>
    <span class="fz-bar-list__value fz-font-normal fz-text-muted">8%</span>
  </div>
  <div class="fz-bar-list__row fz-bar-list__row--neutral">
    <span class="fz-bar-list__label">Draft</span>
    <span class="fz-bar-list__track"><span class="fz-bar-list__bar" style="width: 20%"></span></span>
    <span class="fz-bar-list__value">12</span>
    <span class="fz-bar-list__value fz-font-normal fz-text-muted">20%</span>
  </div>
</div>

Progress

One bar, as long as how far something has got: a score out of a total, steps done, money raised against a goal.

ClassWhat it does
fz-progressThe track. Give it role="progressbar" and the aria-valuenow, aria-valuemin and aria-valuemax it shows, or put the figure beside it in text.
fz-progress--{info,success,warning,danger,neutral}The bar in a status colour, or grey. It is the accent colour otherwise.
fz-progress__barThe bar. Give it its width as a percentage, in a style attribute.
  • For several bars side by side, each with its label and figure, use the bar list.
  • For a colour that is not one of the status colours, set --fz-tone on the track.
Example
<div class="fz-stack fz-gap-2" style="max-width: 20rem">
  <p><span class="fz-text-2xl fz-font-semibold fz-tabular-nums">$12,480</span> <span class="fz-text-muted">of $20,000 raised</span></p>
  <div class="fz-progress fz-progress--success" role="progressbar" aria-valuenow="62" aria-valuemin="0" aria-valuemax="100"><span class="fz-progress__bar" style="width: 62%"></span></div>
</div>

Disclosure

Something that opens in place to show more: the detail behind a summary, a list that is usually not needed.

ClassWhat it does
fz-disclosureOn a <details>. Its <summary> is what is clicked, and the rest is what opens.
fz-disclosure--plainNo box of its own: for one that is inside a card.
  • Say in the summary what is inside, and how much of it if that helps: "Rankings (3)".
  • Add the open attribute to a <details> to have it start open.
  • It grows and shrinks as it opens and closes, unless the person has asked their computer for less motion. Some browsers open and close it at once.
Example
<div class="fz-stack fz-gap-2">
  <details class="fz-disclosure">
    <summary>Mitigations (2)</summary>
    <ul>
      <li>Two staff are present at every session.</li>
      <li>Sessions are held in rooms with a window in the door.</li>
    </ul>
  </details>
  <details class="fz-disclosure" open>
    <summary>Reviews (1)</summary>
    <p class="fz-text-muted">Reviewed on September 12, 2026. No changes needed.</p>
  </details>
</div>

Tabs

A row of tabs, each showing one part of a page in place of the others: the overview, the answers and the assessment of one report.

ClassWhat it does
fz-tabsThe row of tabs. Give it role="tablist" and an aria-label.
fz-tabs__tabOne tab: a <button type="button"> with role="tab", aria-controls naming its panel, and aria-selected="true" on the one that is shown and "false" on the others.
fz-tab-panelWhat a tab shows. Give it role="tabpanel" and an id, and the hidden attribute when its tab is not the one selected.
  • Switching tabs takes a few lines of script, kept with the page: when a tab is clicked, set aria-selected on each tab and hidden on each panel.
  • On paper the tabs are left out and every panel prints, one after the other, so a printed copy has all of it.
  • Use tabs for parts of one thing that are read separately. For a choice that filters or changes what a list shows, use buttons with aria-pressed, or a choice.
Example
<div class="fz-stack fz-gap-4">
  <div class="fz-tabs" role="tablist" aria-label="Report">
    <button type="button" class="fz-tabs__tab" role="tab" aria-controls="tab-overview" aria-selected="true">Overview</button>
    <button type="button" class="fz-tabs__tab" role="tab" aria-controls="tab-interview" aria-selected="false">Interview</button>
    <button type="button" class="fz-tabs__tab" role="tab" aria-controls="tab-assessment" aria-selected="false">Assessment</button>
  </div>
  <div class="fz-tab-panel" id="tab-overview" role="tabpanel">
    <p>Met with the program lead on October 2. Enrolment is up on last year.</p>
  </div>
  <div class="fz-tab-panel" id="tab-interview" role="tabpanel" hidden>
    <p>The answers to each interview question.</p>
  </div>
  <div class="fz-tab-panel" id="tab-assessment" role="tabpanel" hidden>
    <p>The rating, and the reasons for it.</p>
  </div>
</div>

Popover

A panel that floats over the page beside the button that opens it: more about a value, a short list. Nothing on the page moves when it opens.

ClassWhat it does
fz-popoverOn an element with the popover attribute and an id. The <button> that opens it has popovertarget set to that id.
fz-popover__titleA small heading inside it, for each part of what it shows.
fz-popover__sepOn an <hr>: a line between parts.
  • The browser opens and closes it, with no script: it closes when anything else is clicked, or Esc is pressed.
  • Pair it with its button by an anchor name, so it opens beside the button: style="anchor-name: --screens-3" on the button, and style="position-anchor: --screens-3" on the popover. Each pair needs a name of its own, starting with two dashes.
  • It opens under the button, lined up with its start, and moves above it, or across, when there isn't room. Browsers place it there themselves; in one from before they did (Safari before 26, Firefox before 147), Formulize's own script, formulize-ui.js, does it.
  • Keep it short, and put nothing in it that has to be seen: it is for the detail behind something already on the page.
Example
<div class="fz-cluster">
  <button type="button" class="fz-btn" popovertarget="example-popover" style="anchor-name: --example-popover">3 screens</button>
  <div class="fz-popover" id="example-popover" popover style="position-anchor: --example-popover">
    <p class="fz-popover__title">Screens set to Events</p>
    <ul class="fz-list-none fz-p-0 fz-m-0">
      <li>Event registration</li>
      <li>Volunteer sign-up</li>
      <li>Event check-in</li>
    </ul>
    <hr class="fz-popover__sep">
    <p class="fz-text-muted">A change to Events changes all three.</p>
  </div>
</div>

Popover panel

A popover that holds a small form, such as a filter: a few choices to make, with buttons at the foot to apply them or not. Its body scrolls when there is more than fits, and its foot stays in view.

ClassWhat it does
fz-popover--panelBeside fz-popover, on the popover.
fz-popover__headOptional: what stays at the top while the body scrolls, such as a box to find a choice.
fz-popover__bodyThe choices.
fz-popover__footThe buttons, at the end of the row.
  • Open and pair it as a popover. Clicking elsewhere and Esc close it as they close any popover, so treat that as Cancel: listen for its toggle event, and put back anything the panel changed.
  • It can be wider than a popover usually is, up to the width of the window less a margin, for a form that needs the room. Give it a min-width for that.
  • The list filters for checkboxes and date ranges are panels (include/js/filterpopovers.js).
Example
<div class="fz-cluster">
  <button type="button" class="fz-btn" popovertarget="example-panel" style="anchor-name: --example-panel">Status: Any</button>
  <div class="fz-popover fz-popover--panel" id="example-panel" popover style="position-anchor: --example-panel; min-width: 16rem">
    <div class="fz-popover__body">
      <div class="fz-choice-group">
        <label class="fz-choice"><input type="checkbox" class="fz-checkbox"> Draft</label>
        <label class="fz-choice"><input type="checkbox" class="fz-checkbox" checked> Submitted</label>
        <label class="fz-choice"><input type="checkbox" class="fz-checkbox"> Approved</label>
      </div>
    </div>
    <div class="fz-popover__foot">
      <button type="button" class="fz-btn fz-btn--sm" popovertarget="example-panel" popovertargetaction="hide">Cancel</button>
      <button type="button" class="fz-btn fz-btn--sm fz-btn--primary">Apply</button>
    </div>
  </div>
</div>

A list of actions in a popover, opened by a button: the less common things that can be done to one item.

ClassWhat it does
fz-menuBeside fz-popover, on the popover.
fz-menu__itemOne action: a <button type="button">, or an <a> for going somewhere.
fz-menu__item--dangerA destructive action, such as delete.
fz-menu__noteA line under an item's name that says more about it, in a <span> inside the item.
fz-menu__iconAn icon before an item's name: a <span> around an <svg>, first in the item. The name and its note line up beside it.
fz-menu__sepOn an <hr>: a line between groups of items.
  • Open and pair it as a popover. Tab moves between the items.
  • An item can open a dialog with commandfor and command="show-modal"; the menu closes as the dialog opens. An item that does something on the page needs a line of script, which closes the menu with hidePopover() too.
  • A disabled item, or one with aria-disabled="true", is faded and can't be clicked.
  • The item that is in effect, such as the view a list is showing, has aria-current="true", and is shown selected.
  • A group of items can have a small heading: an fz-popover__title, which lines up with the items' names. Put an fz-menu__sep between groups.
  • Lyris's list screens use menus for their view switcher and More actions. Formulize's own list buttons, <input type="button" class="formulize_button">, are made into items there by Lyris's own rules.
Example
<div class="fz-cluster">
  <button type="button" class="fz-btn" popovertarget="example-menu" style="anchor-name: --example-menu">Edit</button>
  <div class="fz-popover fz-menu" id="example-menu" popover style="position-anchor: --example-menu">
    <button type="button" class="fz-menu__item"><span class="fz-menu__icon" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M10.5 2.5l3 3L6 13H3v-3z"/></svg></span>Simple editor<span class="fz-menu__note">Logo, colours, fonts, page width and size</span></button>
    <button type="button" class="fz-menu__item"><span class="fz-menu__icon" aria-hidden="true"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M3 2.5l9.5 4-4 1.5-1.5 4z"/></svg></span>Advanced editor<span class="fz-menu__note">Every part, one at a time</span></button>
    <hr class="fz-menu__sep">
    <button type="button" class="fz-menu__item">Duplicate</button>
    <button type="button" class="fz-menu__item fz-menu__item--danger">Delete</button>
  </div>
</div>

Dialog

A question or a short form over the page, which waits until it is answered: making something, confirming a deletion.

ClassWhat it does
fz-dialogOn a <dialog> with an id. The <button> that opens it has commandfor set to that id and command="show-modal".
fz-dialog--wideWider, for a form with more in it.
fz-dialog__headerThe top: its title, and anything beside it.
fz-dialog__titleThe title: an <h2>. Give the dialog aria-labelledby with its id.
fz-dialog__bodyWhat it says or asks: text, fields. Its children are stacked.
fz-dialog__footerIts buttons, at the end, with the main one last. It stays in view if the dialog is long enough to scroll.
  • The browser opens and closes it, with no script of yours. A button with commandfor and command="close" closes it, and so does Esc. In a browser from before commandfor (Safari before 26.2, Firefox before 144), Formulize's own script, formulize-ui.js, does what those buttons ask. Add closedby="any" to the <dialog> to have a click outside it close it too, in browsers that do that.
  • The page under it can't be scrolled or clicked while it is open, and when it closes, the keyboard is back on the button that opened it.
  • Put the header, body and footer in a <form method="dialog"> and its submit button closes the dialog, with the button's value as the dialog's returnValue. For a form that is sent to the server, use an ordinary <form> instead.
  • Say what the main button does: "Delete look", not "OK".
Example
<div class="fz-cluster">
  <button type="button" class="fz-btn fz-btn--danger" commandfor="example-dialog" command="show-modal">Delete Events…</button>
  <dialog class="fz-dialog" id="example-dialog" aria-labelledby="example-dialog-title">
    <form method="dialog">
      <div class="fz-dialog__header">
        <h2 class="fz-dialog__title" id="example-dialog-title">Delete Events?</h2>
      </div>
      <div class="fz-dialog__body">
        <p>These screens use Events, and will use the site look instead:</p>
        <ul>
          <li>Event registration</li>
          <li>Volunteer sign-up</li>
        </ul>
        <p class="fz-text-muted">This can't be undone.</p>
      </div>
      <div class="fz-dialog__footer">
        <button type="button" class="fz-btn" commandfor="example-dialog" command="close">Cancel</button>
        <button type="submit" class="fz-btn fz-btn--danger" value="delete">Delete look</button>
      </div>
    </form>
  </dialog>
</div>

Tree

A table whose rows are nested under other rows, with lines joining each row to the one it is under: things based on other things, parts of a whole.

ClassWhat it does
fz-treeBeside fz-table, on the <table>. Its rows have no lines between them, and the one the pointer is on is shaded. One line of text a row.
fz-tree__cellThe first cell of each row, with style="--fz-depth: N": 0 for a row at the top, 1 for a row under it, and so on.
fz-tree__lineAn empty <span> in the cell: a line straight down through the row, for a row above it with a later row next to it. One for each such row, with --fz-level set to that row's depth less one.
fz-tree__line--elbowThe line into the row, from the row it is under. --fz-level is the row's depth less one. Every row but the top ones has one.
fz-tree__line--stemThe line down from the row to the first row under it. --fz-level is the row's depth. Only on a row with rows under it.
  • The lines are worked out from the rows, so they are usually written by whatever writes the rows: a loop in a template, or the code that builds the page.
  • A row that is not the last one under its row has a straight fz-tree__line beside its elbow, at the same level, to carry the line on to the next one.
  • For a tree wider than its container, wrap it in an element with fz-overflow-x-auto, as for any table.
Example
<div class="fz-card fz-card--flush fz-overflow-x-auto">
  <table class="fz-table fz-tree">
    <thead><tr><th>Look</th><th>Used by</th></tr></thead>
    <tbody>
      <tr>
        <td class="fz-tree__cell" style="--fz-depth: 0"><span class="fz-tree__line fz-tree__line--stem" style="--fz-level: 0"></span>Default</td>
        <td>The site</td>
      </tr>
      <tr>
        <td class="fz-tree__cell" style="--fz-depth: 1"><span class="fz-tree__line fz-tree__line--elbow" style="--fz-level: 0"></span><span class="fz-tree__line" style="--fz-level: 0"></span><span class="fz-tree__line fz-tree__line--stem" style="--fz-level: 1"></span>Events</td>
        <td>3 screens</td>
      </tr>
      <tr>
        <td class="fz-tree__cell" style="--fz-depth: 2"><span class="fz-tree__line" style="--fz-level: 0"></span><span class="fz-tree__line fz-tree__line--elbow" style="--fz-level: 1"></span>Events, compact</td>
        <td>2 screens</td>
      </tr>
      <tr>
        <td class="fz-tree__cell" style="--fz-depth: 1"><span class="fz-tree__line fz-tree__line--elbow" style="--fz-level: 0"></span>Reports</td>
        <td>2 screens</td>
      </tr>
      <tr>
        <td class="fz-tree__cell" style="--fz-depth: 0">Partner portal</td>
        <td>1 screen</td>
      </tr>
    </tbody>
  </table>
</div>

Embed

Something from elsewhere shown in the page: a map, a video, another site's page. It has a shape of its own, so the page doesn't jump when what is in it arrives.

ClassWhat it does
fz-embedAround an <iframe>, <video> or <img>, which fills it. Wide: 16 by 9.
fz-embed--squareSquare.
  • Give an <iframe> a title that says what it shows.
  • It is as wide as the space it is in. To limit it, put it in a column of a layout class, or give it a max-width in a style attribute.
Example
<div class="fz-embed" style="max-width: 24rem">
  <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 9'%3E%3Crect width='16' height='9' fill='%23d1d1cb'/%3E%3C/svg%3E" alt="A grey rectangle, standing in for a map">
</div>

Prose

Running text that someone wrote: an article, instructions, an agreement, the contents of a rich text field, a reply from the AI assistant. Its paragraphs, headings, lists, code and tables are sized and spaced for reading.

ClassWhat it does
fz-proseOn the element around the text.
  • The elements inside need no classes: write ordinary <p>, <h2>, <ul>, <blockquote>, <code>, <pre> and <table>. That makes it the way to show text that came from an editor, and to give text headings and lists in a derived value, where the elements inside cannot keep classes of their own.
  • A code block (<pre>) and a table wider than the text scroll sideways, rather than making the page wider.
  • It is as wide as the space it is in. Long lines are hard to read, so put it in fz-container fz-container--narrow, or in a column.
  • It is for text. Lay out a page with the layout classes, not with this.
Example
<div class="fz-prose">
  <h2>Before the first session</h2>
  <p>Thank you for hosting a workshop. This page sets out what we bring, and what we ask of you.</p>
  <h3>We will bring</h3>
  <ul>
    <li>All the materials for each activity</li>
    <li>Two instructors, who arrive 30 minutes early to set up</li>
  </ul>
  <h3>We ask that you</h3>
  <ol>
    <li>Have a room with tables and a sink nearby</li>
    <li>Tell us about any allergies in the group</li>
  </ol>
  <blockquote>A teacher stays in the room for the whole session.</blockquote>
  <h3>Times</h3>
  <table>
    <thead><tr><th>Session</th><th>Arrive by</th></tr></thead>
    <tbody><tr><td>Morning</td><td>8:30am</td></tr><tr><td>Afternoon</td><td>12:30pm</td></tr></tbody>
  </table>
  <p>Book through the <code>workshops</code> form.</p>
</div>

Code block

Code, data or a log shown on its own: an example to copy, the response from a web service, the output of a script.

ClassWhat it does
fz-code-blockOn a <pre>: monospaced, its lines kept as they were written, scrolling sideways when one is too long.
fz-code-block--wrapLong lines wrap instead, for data such as JSON, where a line can be very long.
  • For code in running text, such as instructions with a command in them, use fz-prose, which styles the <pre> and <code> inside it.
  • To cap the height, set --fz-code-block-max-height on it, in a style attribute: it scrolls inside that height.
  • Escape what goes inside it, with htmlspecialchars() in PHP, so the code shows as text.
Example
<div class="fz-stack fz-gap-3">
  <pre class="fz-code-block"><code>&lt;div class="fz-card"&gt;
  &lt;h3 class="fz-card__title"&gt;Robotics club&lt;/h3&gt;
&lt;/div&gt;</code></pre>
  <pre class="fz-code-block fz-code-block--wrap" style="--fz-code-block-max-height: 6rem">{ "form_id": 12, "title": "Applications", "elements": [ { "handle": "applications_student", "type": "text" }, { "handle": "applications_school", "type": "text" }, { "handle": "applications_status", "type": "radio" } ] }</pre>
</div>

Matrix

A grid of cells, each coloured by what it stands for: a risk matrix, a heat map, a chart of what is free and what is taken.

ClassWhat it does
fz-matrixOn a <table>. Its columns are all the same width.
fz-matrix__cellA cell: on a <span> inside the <td>, or on a <button> there when the cell can be chosen. Square, and grey.
fz-matrix__cell--{info,success,warning,danger}A cell in a status colour.
fz-matrix__cell--selectedThe cell that is chosen, or the one a value falls in: outlined.
fz-matrix__cell--mutedFaded: a cell that is beside the point while others are being looked at.
  • Give the rows and columns their headings with <th>, and say in each cell what it stands for: the colour alone is not enough.
  • The colour is a tint, and the text stays the normal text colour, as in a badge.
  • For colours that are not the status colours, such as the steps of a scale, set --fz-tone on the cell.
Example
<table class="fz-matrix" style="max-width: 24rem">
  <thead>
    <tr><td></td><th scope="col">Minor</th><th scope="col">Moderate</th><th scope="col">Major</th></tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">Unlikely</th>
      <td><span class="fz-matrix__cell fz-matrix__cell--success">Low</span></td>
      <td><span class="fz-matrix__cell fz-matrix__cell--success">Low</span></td>
      <td><span class="fz-matrix__cell fz-matrix__cell--warning">Medium</span></td>
    </tr>
    <tr>
      <th scope="row">Possible</th>
      <td><span class="fz-matrix__cell fz-matrix__cell--success">Low</span></td>
      <td><span class="fz-matrix__cell fz-matrix__cell--warning fz-matrix__cell--selected">Medium</span></td>
      <td><span class="fz-matrix__cell fz-matrix__cell--danger">High</span></td>
    </tr>
    <tr>
      <th scope="row">Likely</th>
      <td><span class="fz-matrix__cell fz-matrix__cell--warning">Medium</span></td>
      <td><span class="fz-matrix__cell fz-matrix__cell--danger">High</span></td>
      <td><span class="fz-matrix__cell fz-matrix__cell--danger">High</span></td>
    </tr>
  </tbody>
</table>