HelixOverviewComponentsMapsGraphIntroductionInstallationDeriving a themeSandboxTheme builderVisual testingCortexDocsEngramBuildingTraceBuildingRecallDeclaredServicesInsightsGet in touchSource on GitHub
Essentials
State and controlA property you set is a starting value, and you can assign it again later to
correct it. Set value, open, checked, or active on a Helix element. The
element starts there and then manages its own state, as <input value="Ada">
does.<helix-inputlabel="Name"value="Ada"></helix-input>When you type in the input, the value changes. When you assign a new value
later, the value changes to what you assigned. The control has no modes, and you
do not need to connect anything before it responds.Until August 26, 2026, this page said the opposite. Setting a property claimed
ownership of the state, so the element announced its changes and applied none
of them until you set the new value. That mode is removed. If you canceled
changes by withholding a state update, rewrite that code to call
preventDefault(). Nothing else has to change.Cancel or take over a changeYou take control one change at a time. Each change sends a cancelable event
before it is applied:input.addEventListener('helix-before-change',(event)=>{if(locked){event.preventDefault()}})preventDefault() cancels that one change, and nothing moves. To manage the
state yourself, cancel the change and assign your own value:<helix-inputlabel="Name"onhelix-before-change={(event)=>{event.preventDefault()flushSync(()=>setName(sanitize(event.detail.value)))}}value={name}/>Assign your value before the next key arrives, or that key lands in the text
from before your value. Vue, Svelte, and Solid apply a bound value in time. In
React, set the state inside flushSync, as React explains. In
Angular without zone.js, call detectChanges() after you set the signal, as
Angular explains.The platform uses the same pattern. You call preventDefault() to cancel:a form submita link clicka beforeinput eventYou do not declare anything in advance, and Helix does not infer anything from
which properties you set.The imperative method reports the same result:constapplied=item.requestOpen(true)false means something canceled the change.Follow the state with the change eventMost of the time you do not need to cancel anything. Listen for the change
event and let the element apply the change:<helix-inputlabel="Name"onChange={(event)=>setName(event.currentTarget.value)}/><helix-inputlabel="Name"onchange="save(this.value)"></helix-input>The native event carries no detail. The element has already applied the change
when the event fires, so the value is on the target, where handlers expect to
find it.default-value sets the value a reset returns to<helix-inputdefault-value="Ada"label="Name"></helix-input>default-value is the value a reset returns to. value is the control's
current value. The pair comes from the platform. A native input reflects its
valuecontent attribute to defaultValue, and it keeps a dirty flag. Once a
user has typed, the flag stops the attribute from changing the value.A form reset returns to a value or checked you wrote in markup, as it
does on a native control: <input value="Ada"> and <helix-input value="Ada">
both reset to Ada. Where default-value or default-checked is written, the
reset returns to that instead. The attribute is read at the reset, and a
framework that sets value as a property writes no attribute, so write the
starting value of a form control as default-value, or default-checked for a
checkbox or a switch, when a reset has to return to it.State is published as a custom statehelix-accordion-item:state(open){… }open is not reflected to an attribute, and neither is value. A value written
back as an attribute would put whatever the last user typed into the markup. A
serialized element would then hold the user's text, and you could not tell it
apart from the starting value you wrote. helix-input takes type="password".
A reflected value would carry the password past every session-replay masker
that masks .value and input[value].Helix publishes the resolved boolean as a custom state, and the attribute keeps
the value you wrote. These are the most common, and each component's reference
page lists the states it publishes::state(open):state(checked):state(invalid):state(read)They work in your own stylesheet as an attribute selector did:helix-checkbox:state(checked)::part(control){… }helix-input:state(invalid)::part(field){… }Until August 27, 2026, these states were the data-open and data-checked
attributes. An attribute is in the DOM, and the element writes it on its first
update. A framework that renders your markup on the server and then hydrates it
finds attributes that its own render never produced. React reports that as a
hydration mismatch and does not fix it. A custom state is not in the DOM. It
reaches your stylesheet and leaves the markup alone, so the server render and
the client render always match. If you selected on the old names, replace each
one with its custom state.