Core concepts
View as MarkdownTwo contracts are easy to get wrong because neither has a visible symptom until it matters: which value survives a form reset, and what happens to a property you assign before the element definition loads. Everything else about a component is on its own page — this covers the two rules that span all of them.
Authored defaults and live state
Section titled “Authored defaults and live state”The value attribute you author is the default. The value property is live state. They
are separate storage, exactly as they are on a native <input>:
<ui-select value="draft" name="status">…</ui-select>const select = document.querySelector('ui-select')
select.value // 'draft' — live state, currently equal to the defaultselect.value = 'published' // live state movesselect.defaultValue // 'draft' — the authored attribute is untouchedselect.getAttribute('value') // 'draft'Two consequences follow.
A form reset returns to the authored attribute, not to whatever was assigned last.
Form-associated elements — Select, Combobox, Listbox, and OTP Field — implement formResetCallback
by restoring defaultValue, so <button type="reset"> puts status back to draft.
Assigning the property dispatches nothing. ui-before-change and ui-change describe user
transitions and carry the originating event in detail.originalEvent. A programmatic assignment has
no originating event, so it updates the DOM silently. Code that assigns a value already knows it
did; if you need your own listeners to run, dispatch your own event.
To move the default itself — so a later reset returns somewhere new — set the attribute:
select.setAttribute('value', 'published')The helpers the elements use to keep the two apart are exported from
@timelessui/components/value-state, if you are building
something on the same contract.
Properties assigned before upgrade
Section titled “Properties assigned before upgrade”A framework often has a reference to an element before customElements.define has run for it. An
assignment at that moment lands as an own property on the instance, shadowing the accessor the
definition will later install on the prototype. In most custom-element libraries the value is then
lost.
Timeless replays it. On connection, each declared attribute and property that exists as an own property is deleted and re-assigned, which runs the real setter — and this happens before the component’s own connection logic, so the element never observes a stale value:
const select = document.createElement('ui-select')select.value = 'published' // no definition yet — a plain own property
await import('@timelessui/components/register/ui-select')document.body.append(select) // upgrade, then replay, then connect
select.value // 'published'This is why registration is safe to load late, and why a server-rendered page can hydrate and assign state without ordering the registration import first. See Packages and entrypoints for the import-safety rules and your framework’s guide under Frameworks for where the assignment usually lives.
Where the rest lives
Section titled “Where the rest lives”- Anatomy, attributes, parts, states, events — each component’s own page, generated from the contract the stylesheets are proven against.
- The two kinds of component, and how each is configured — AI agents.
- Layers, tokens, and overriding styles — Theming.
- Import safety and server rendering — Packages and entrypoints.
- What the platform handles and what degrades — Browser support.