Vitre UI Components

Components are ordinary semantic HTML marked with a data-kind directive. vitre.css styles them, and vitre.js adds behavior to the ones that need it.

Every component on this page degrades without JavaScript. Load vitre.css alone and the markup still renders as readable, styled content.

Component Anatomy

A Vitre component is not a custom element and not a class name. It is a semantic element carrying a data-kind directive, plus the ARIA roles the pattern already calls for. That means you can adopt one component without adopting a framework, and remove one by deleting an attribute.

Component Directive Needs JS Without JS
Alert data-kind="alert" Only for dismiss and timeout Renders as a styled, permanent alert
Tabs data-kind="tabs" Yes, for selection Tab strip hides, all panels stack
Splitter data-kind="splitter" Only for focus and orientation Renders as a styled divider
Navigation data-kind="nav" Yes, for SPA routing Links navigate normally
Theme toggle data-kind="theme-toggle" Yes, to render the button Nothing renders; theme follows the OS
Buttons data-variant, data-color No Fully styled
Dialogs role="dialog", <dialog> No Fully styled

CSS custom properties for every component live in the CSS Variable Reference.

Alerts

An alert is any element with data-kind="alert". Use data-color for intent, and add role="status" or role="alert" only when live-region announcement is wanted.

Demo

A plain informational alert.
Saved successfully. This one can be dismissed.
This one dismisses itself after twelve seconds.
Something went wrong.

HTML

<div data-kind="alert" data-color="success" role="status" dismiss>
  <span data-v-content>Saved successfully.</span>
</div>

<div data-kind="alert" data-color="warning" role="alert" dismiss timeout="12">
  <span data-v-content>Dismisses itself after twelve seconds.</span>
</div>

When using dismiss, wrap the text in a child carrying data-v-content. That reserves the layout slot for the close button so the alert does not reflow when vitre.js injects it.

Behavior

AttributeEffect
dismiss Appends a ghost icon button that removes the alert.
timeout="12" Removes the alert after the given number of seconds.

Removal emits a bubbling vitre:dismiss event whose detail.source is the alert element.

document.addEventListener("vitre:dismiss", (event) => {
  console.log("dismissed", event.detail.source);
});

Without vitre.js the alert renders as a styled, permanent block.

Tabs

Wrap a role="tablist" and its role="tabpanel" sections in data-kind="tabs". You write the roles the pattern already requires; vitre.js generates the rest of the ARIA wiring. Every tabbed panel on this page is this component.

Demo

You are looking at it. The strip above is a tablist and this is its first panel.

Tabs can contain any content, including other components.

HTML

<div data-kind="tabs">
  <div role="tablist" aria-label="Account settings">
    <button role="tab">Profile</button>
    <button role="tab">Notifications</button>
  </div>

  <section role="tabpanel">
    <h4>Profile</h4>
    <p>Profile settings.</p>
  </section>

  <section role="tabpanel">
    <h4>Notifications</h4>
    <p>Notification settings.</p>
  </section>
</div>

Tabs pair with panels in document order. When the order differs, point a tab at its panel explicitly with aria-controls.

Behavior

vitre.js generates every attribute you did not write:

  • Missing id values on tabs and panels
  • aria-controls and aria-labelledby pairing
  • aria-selected and a roving tabindex
  • type="button" on tab buttons, so they never submit a form
KeyAction
Previous or next tab, wrapping at both ends
Same, when the tablist is vertical
HomeFirst tab
EndLast tab

Set aria-selected="true" on a tab to choose the initial panel; otherwise the first is selected. Changing tabs emits a bubbling vitre:tabchange event.

document.addEventListener("vitre:tabchange", (event) => {
  console.log(event.detail.index, event.detail.panel);
});

Without vitre.js the tab strip is hidden and every panel renders stacked, so give each panel a heading. Content stays reachable instead of leaving dead buttons behind.

Vertical

Add aria-orientation="vertical" to the tablist.

<div role="tablist" aria-orientation="vertical">

First vertical panel.

Second vertical panel.

Splitter

A data-kind="splitter" with role="separator" renders a resizable divider handle between two panels. It highlights on hover, on focus, and while being dragged.

Demo

Left panel
Right panel

Tab to the handle to see the focus treatment.

HTML

<div style="display: flex">
  <div>Left panel</div>
  <span data-kind="splitter" role="separator" aria-valuenow="50"></span>
  <div>Right panel</div>
</div>

For a horizontal divider add aria-orientation="horizontal", which switches the cursor to row-resize.

Behavior

vitre.js makes the handle focusable and gives it a default aria-orientation when one is not supplied. Pane sizing stays your application's responsibility — Vitre does not own your layout model.

Theme Toggle

An empty element with data-kind="theme-toggle" becomes a light/dark switch. vitre.js renders the button, so nothing appears without it.

Demo

Toggle this page between light and dark:

HTML

<span data-kind="theme-toggle"></span>

Behavior

The generated button toggles data-theme between light and dark on the root <html> element and stores the choice in localStorage under vitre-theme. The stored value is restored on the next page load, before components are enhanced.

With no stored preference and no data-theme, Vitre follows the operating system through prefers-color-scheme.

Buttons

Native buttons receive a complete default treatment with no JavaScript. Variants use data-variant, and semantic intent uses data-color.

Demo

HTML

<button>Default</button>
<button data-variant="flat">Flat</button>
<button data-variant="outline" data-color="error">Delete</button>

Each variant decides whether data-color is used as a fill, foreground, border, or hover tint. The same data-color values also apply to progress and meter.

Dialogs

Vitre styles native dialogs and dialog-like semantic regions, with no JavaScript.

Demo

Dialog Preview

This non-modal preview uses dialog semantics without opening a native modal.

HTML

<dialog>
  <article>
    <h2>Confirm action</h2>
    <button>Close</button>
  </article>
</dialog>

Semantic Components

Before reaching for a directive, use real HTML structure. Sections become readable panels, articles become self-contained cards, and asides become callouts — with no attributes at all.

Demo

Release Notes

A self-contained semantic card.

Version 1.6.0

HTML

<article>
  <header>
    <h3>Release Notes</h3>
    <p>A self-contained semantic card.</p>
  </header>
  <aside>Nested asides become callouts.</aside>
  <footer><small>Version 1.6.0</small></footer>
</article>