React
Install and TypeScript
Section titled “Install and TypeScript”pnpm add @timelessui/componentsReact 19 supports custom-element properties on the client and primitive attributes during server rendering. Import Timeless JSX declarations once in your application types:
import '@timelessui/components/react'The declaration is types-only. Timeless does not add a runtime wrapper or React dependency.
CSS and registration
Section titled “CSS and registration”Import CSS and registration from a client entrypoint:
import '@timelessui/components/css/tokens.css'import '@timelessui/components/css/listbox.css'import '@timelessui/components/define/ui-listbox'Load definition imports from a client entrypoint. CSS and class-only imports are safe during server rendering.
Markup and hydration
Section titled “Markup and hydration”Use the element directly:
export function StatusPicker() { return ( <ui-listbox value="draft" role="listbox" aria-label="Status"> <div role="option" data-ui-value="draft"> Draft </div> <div role="option" data-ui-value="ready"> Ready </div> </ui-listbox> )}Primitive values render as attributes on the server. Assign object or array properties after the element upgrades when a component exposes them. React hydrates the authored Light DOM, then the browser upgrades the custom element without replacing that anatomy.
Events
Section titled “Events”Attach namespaced custom events with a ref and addEventListener when an application needs typed,
cancelable transition handling. onui-change and onui-before-change props are also declared and
carry the detail type the element actually dispatches, so a ui-tabs handler receives a
CustomEvent<TabsChangeDetail> rather than a generic one. Detail types import from
@timelessui/components/events and from each element’s own entrypoint.
on* props on custom elements and non-string attribute values both require React 19. On React 18
the declarations still type your markup, but pass primitives as attributes and bind events with a
ref.
CSS-only components
Section titled “CSS-only components”Most Timeless components are plain CSS over native HTML and have no custom element at all. Those are
a root class plus data-ui-* on a native tag, and JSX cannot check that per element without
loosening every element in your app. Use the typed helper:
import { uiAttributes } from '@timelessui/components/attributes'
export function PublishButton() { return ( <button {...uiAttributes('button', { variant: 'primary', size: 'lg' })} type="button"> Publish </button> )}variant accepts only the seven button variants, and Card’s filled is a type error on a Button.
See Editor setup for what each editor completes.