Dialog Preview
This non-modal preview uses dialog semantics without opening a native modal.
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.
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.
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.
<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.
| Attribute | Effect |
|---|---|
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.
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.
You are looking at it. The strip above is a tablist and this is its first panel.
<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.
vitre.js generates every attribute you did not write:
id values on tabs and panelsaria-controls and aria-labelledby pairingaria-selected and a roving tabindextype="button" on tab buttons, so they never submit a form| Key | Action |
|---|---|
| ← → | Previous or next tab, wrapping at both ends |
| ↑ ↓ | Same, when the tablist is vertical |
| Home | First tab |
| End | Last 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.
Add aria-orientation="vertical" to the tablist.
<div role="tablist" aria-orientation="vertical">
First vertical panel.
Second vertical panel.
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.
Tab to the handle to see the focus treatment.
<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.
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.
An empty element with data-kind="theme-toggle" becomes a light/dark
switch. vitre.js renders the button, so nothing appears without it.
Toggle this page between light and dark:
<span data-kind="theme-toggle"></span>
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.
Vitre styles native dialogs and dialog-like semantic regions, with no JavaScript.
This non-modal preview uses dialog semantics without opening a native modal.
<dialog>
<article>
<h2>Confirm action</h2>
<button>Close</button>
</article>
</dialog>
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.
A self-contained semantic card.
<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>