Skip to content

Packages and entrypoints

View as Markdown
Package Role
@timelessui/components Everything a consumer needs to build an interface: CSS, custom elements, contracts, and utilities. This is the only package you install for components.
@timelessui/color CSS color parsing, conversion, gamut, and contrast utilities. It depends on nothing, and arrives as a dependency of the components; install it directly only if you want the color math without them.
@timelessui/core The internal custom-element authoring layer the components are built on. It arrives as a dependency; you do not import it directly, and it is not part of the public API.
Terminal window
pnpm add @timelessui/components
Import Contains Side effects
@timelessui/components Contracts, design-token names, permitted attribute values, event types, and every element class. None
@timelessui/components/{element} One enhanced element’s class and helpers, for example @timelessui/components/dialog. None
@timelessui/components/register/ui-{element} Registers one custom element as it is imported. Calls customElements.define
@timelessui/components/register Registers every public custom element as it is imported. Calls customElements.define
@timelessui/components/define/ui-{element} One define{Element}Element() function. Registers nothing until you call it. None
@timelessui/components/define Every define function, plus defineTimelessElements(). Registers nothing until you call it. None
@timelessui/components/css/tokens.css The cascade-layer order and color-scheme. Required, and imported first. Stylesheet
@timelessui/components/css/core/{component}.css One component’s behavior: anchoring, box participation, scrolling, native resets. Required. Stylesheet
@timelessui/components/css/core.css Every core/ file. Required, and the tier you keep when you replace the theme. Stylesheet
@timelessui/components/css/themes/atmosphere/tokens.css Every --ui-* value. Required by any Atmosphere component file, which carries no fallbacks. Stylesheet
@timelessui/components/css/themes/atmosphere/{component}.css One component’s look. Optional — this is the tier you replace. Stylesheet
@timelessui/components/css/themes/atmosphere.css The required tiers plus every Atmosphere stylesheet. The one import a prototype needs. Stylesheet
@timelessui/components/collection Locale-aware matching and disabled-aware keyboard navigation. None
@timelessui/color Color parsing, serialization, conversion, gamut, and WCAG contrast. Its own package. None
@timelessui/components/events Transition event and detail types. None
@timelessui/components/value-state Authored-default and live-value helpers. None
@timelessui/components/attributes Typed attribute builder for CSS-only components. None
@timelessui/components/validate Development-time check that authored markup matches the contracts. None
@timelessui/components/react React 19 intrinsic-element declarations. Types only. None
@timelessui/components/preact Preact intrinsic-element declarations. Types only. None
@timelessui/components/solid Solid intrinsic-element declarations. Types only. None
@timelessui/components/vue Vue GlobalComponents declarations. Types only. None
@timelessui/components/svelte Svelte svelteHTML.IntrinsicElements declarations. Types only. None

Only enhanced elements have a class entrypoint. CSS-only primitives such as Button, Alert, and Card are classes and attributes rather than JavaScript, so they have no per-component module at all — their permitted values live on the root import instead. Two pairs share one module behind two subpaths, so importing either name gives you both classes: radio-group and checkbox-group are both choice-group, and toaster and toast are both toast.

Enhanced elements are composed, so an entrypoint pulls the modules it is built on. Select contains Listbox, the option layer, Popover, and the anchoring layer, which is why it is the heaviest single import in the package.

Bundled and minified, @timelessui/components/select is about 13.5 kB gzipped. Adding @timelessui/components/combobox to the same page brings the pair to about 16 kB rather than to the sum of the two, because everything under both of them is shared. Six enhanced elements — Select, Combobox, Listbox, Popover, Menu, and Dialog — come to about 21 kB together. There is no framework runtime underneath any of it, and a CSS-only component such as Button or Card adds no JavaScript at all.

Those are measurements rather than budgets, taken with esbuild at default settings; your bundler will land somewhere nearby. The package ships unminified so your own pipeline decides.

The root import is the machine-readable version of every component reference page:

import { buttonVariants, componentContracts } from '@timelessui/components'
buttonVariants
// ['primary', 'secondary', 'outline', 'ghost', 'danger', 'danger-outline', 'link']
componentContracts.tabs.parts.filter((part) => part.required).map((part) => part.selector)
// ["[role='tablist']", "[role='tab']", "[role='tabpanel']"]

componentContracts records each component’s root, stylesheets, attributes with their permitted values and defaults, authored parts, public state, and events. Every attribute that takes a fixed set of values also names the array that set is exported as, in its set field.

The build proves those values against the stylesheets in both directions: a value a stylesheet selects must be declared, and a declared value must be selected or be the default, which is the base rule and so has no selector of its own. pnpm -F @timelessui/components run contracts:validate fails either way, which is why the reference tables cannot drift from the CSS.

Permitted values are also exported individually as as const arrays with matching union types, each declared exactly once and generated from the same source as the contracts: buttonVariants, buttonSizes, alertVariants, spinnerVariants, badgeVariants, avatarShapes, avatarStatuses, cardVariants, linkVariants, listVariants, separatorVariants, separatorOrientations, skeletonShapes, skeletonWidths, groupOrientations, primitiveSizes, primitiveDensities, compactDensities, tableAlignments, breadcrumbSeparators, formControlSizes, fieldLayouts, formDensities, choiceGroupOrientations, floatingPlacements, tabsOrientations, tabsActivations, dialogKinds, sheetPositions, popoverRoles, hoverCardVariants, menuOrientations, toolbarOrientations, collectionAlignments, optionFilterModes, toasterPlacements, toasterStacks, toggleGroupOrientations, toggleGroupSelections, and colorPickerFormats.

Arrays with identical values keep separate names because they are separate public exports. buttonSizes, primitiveSizes, and formControlSizes are all sm | md | lg, and each names the set its own components implement.

uiTokenGroups names every public custom property, grouped by purpose, and isUIToken narrows a string against the set. Both name the theme-neutral --ui-* vocabulary rather than Atmosphere, which is one set of values for it. See Theming for the values and how to override them.

@timelessui/components/custom-elements.json is exposed through the package’s customElements field. It declares attributes with union types and defaults, reflecting properties under their real names, events with the detail type each element actually dispatches, CSS custom properties, and custom states — for editors, generators, and downstream tooling.

Authored parts appear under a namespaced timeless:parts key rather than the manifest’s cssParts, each with the selector it is addressed by. cssParts describes ::part(), which only crosses a shadow boundary; Timeless anatomy is Light DOM you author yourself, so [data-ui-part~='trigger'] is the real contract and claiming a shadow one would be wrong.

Every framework typing is generated from the manifest and is types-only: importing one adds no runtime code, no wrapper, and no dependency on that framework. Import the one you need once in your application types.

Import Augments
@timelessui/components/react react and react/jsx-runtime JSX
@timelessui/components/preact preact and preact/jsx-runtime JSX
@timelessui/components/solid solid-js JSX
@timelessui/components/vue @vue/runtime-dom GlobalComponents
@timelessui/components/svelte svelteHTML.IntrinsicElements

Each declares, per element, the attributes an author writes typed to their permitted values, the DOM properties those attributes reflect under the element’s real property names, and the element’s own events with its own detail type. data-* and aria-* stay open as an escape hatch.

Two boundaries worth knowing:

  • React 19 is required for on* event props on custom elements and for non-string attribute values. On React 18 the declarations still type your markup; pass primitives as attributes and bind events with a ref.
  • Angular has no type-level checking for unknown elements beyond CUSTOM_ELEMENTS_SCHEMA, which disables checking rather than adding it. There is nothing to ship for Angular, so nothing is shipped. Timeless elements work there; their attributes are simply unchecked.

Qwik is not covered. Its JSX module moved between major versions and its custom-event prop convention differs from the frameworks above, so no declaration is published rather than one that might be wrong.

ui-* tags are tags, so every typing above and the editor data complete them precisely. CSS-only components are a root class plus data-ui-* on a native tag, which no editor can complete per element. @timelessui/components/attributes is the typed answer: uiAttributes returns the attribute object and uiAttributeString the serialized form, both keyed to each component’s permitted values. Neither is reachable from the default import.

See Editor setup for why the gap exists, worked examples, and the development-time validator.

Every import above is side-effect free except the stylesheets and the register/* entrypoints. Class imports and define/* imports never call customElements.define, so they are safe in server code and test utilities — define/* hands you the function and leaves the calling to you.

register/* is the one that registers on import, and it is inert on the server, where there is no customElements to define into. That is deliberate: a universal module can import it without crashing the render, and the client bundle re-imports the same module and registers for real. The consequence is worth stating plainly — a register/* import that only ever executes on the server registers nothing at all, so it belongs in code the browser loads. When you would rather be explicit about the timing, import define/* and call the function yourself.

See Core concepts for what registration does in a browser realm.