Packages and entrypoints
View as MarkdownPackages
Section titled “Packages”| 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. |
pnpm add @timelessui/componentsEntrypoints
Section titled “Entrypoints”| 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.
What an entrypoint weighs
Section titled “What an entrypoint weighs”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.
Contracts and permitted values
Section titled “Contracts and permitted values”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.
Design tokens
Section titled “Design tokens”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.
Custom Elements Manifest
Section titled “Custom Elements Manifest”@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.
Framework typings
Section titled “Framework typings”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.
Authoring CSS-only components
Section titled “Authoring CSS-only components”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.
Server rendering
Section titled “Server rendering”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.