Skip to content

Vanilla

Terminal window
pnpm add @timelessui/components

Import styles and definitions from an application module:

import '@timelessui/components/css/tokens.css'
import '@timelessui/components/css/button.css'
import '@timelessui/components/css/toggle.css'
import '@timelessui/components/define/ui-toggle-group'

Then author normal HTML:

<ui-toggle-group selection="single" aria-label="Alignment">
<button class="ui-button ui-toggle" value="left" aria-pressed="true">Left</button>
<button class="ui-button ui-toggle" value="center" aria-pressed="false">Center</button>
</ui-toggle-group>

The same markup is emitted by a server or copied into a static HTML document. The complete Light DOM is readable before registration, then the explicit definition upgrades it in the browser.

Properties are useful for live state. Attributes provide authored defaults and reset state:

const group = document.querySelector('ui-toggle-group')
group.addEventListener('ui-before-change', (event) => {
if (!canChange(event.detail.value)) event.preventDefault()
})
group.addEventListener('ui-change', (event) => save(event.detail.value))

Each element declares the detail type it dispatches, so event.detail is typed per element rather than shared: a ui-toggle-group change carries ToggleGroupChangeDetail. No hydration runtime is required.

There is no JSX layer here, so nothing type-checks your HTML by default. Two things close that gap:

  • Register the shipped editor data once, and ui-* tags, their attributes, and their permitted values complete in any .html file. See Editor setup.
  • For markup assembled in TypeScript, @timelessui/components/attributes gives CSS-only components a typed surface, and @timelessui/components/validate reports authored values that no contract permits:
import { uiAttributeString } from '@timelessui/components/attributes'
const markup = `<button ${uiAttributeString('button', { variant: 'primary' })} type="button">Publish</button>`
if (import.meta.env.DEV) {
const { validateTimelessMarkup } = await import('@timelessui/components/validate')
validateTimelessMarkup()
}

The validator walks the document and warns about a data-ui-* attribute no contract declares or a value outside its permitted set — the class of typo that renders without complaint.