---
title: React
description: Use custom elements directly with opt-in JSX types and no runtime wrapper.
editUrl: true
head: []
template: doc
sidebar:
  order: 2
  hidden: false
  attrs: {}
pagefind: true
draft: false
---

## Install and TypeScript

```sh
pnpm add @timelessui/components
```

React 19 supports custom-element properties on the client and primitive attributes during server
rendering. Import Timeless JSX declarations once in your application types:

```ts
import '@timelessui/components/react'
```

The declaration is types-only. Timeless does not add a runtime wrapper or React dependency.

## CSS and registration

Import CSS and registration from a client entrypoint:

```ts
import '@timelessui/components/css/tokens.css'
import '@timelessui/components/css/core/listbox.css'
import '@timelessui/components/css/core/options.css'
import '@timelessui/components/css/themes/atmosphere/tokens.css'
import '@timelessui/components/css/themes/atmosphere/listbox.css'
import '@timelessui/components/css/themes/atmosphere/options.css'
import '@timelessui/components/register/ui-listbox'
```

Load definition imports from a client entrypoint. CSS and class-only imports are safe during server
rendering.

## Markup and hydration

Use the element directly:

```tsx
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

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

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:

```tsx
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](/docs/getting-started/editor-setup/) for what each editor completes.