Controlled and uncontrolled state
Stateful components expose a current property, such as value, open, or expanded, and
a matching initial default, such as defaultValue, defaultOpen, or defaultExpanded.
Choose who owns that state for each component. The contract is the same in plain JavaScript
and in a framework; each component's API lists its properties and event payloads.
Initialize uncontrolled state
Use the matching default* property or kebab-case attribute for an initial value. The component
then owns changes made through its controls. For example, a disclosure can start open:
<rc-disclosure default-open>
<details open>
<summary>Delivery details</summary>
<p>Orders ship within two working days.</p>
</details>
</rc-disclosure>
The native open attribute also provides the initial state before JavaScript loads. Keep the
native fallback and the component's initial default in agreement.
Set defaults before initialization. They are initial values, not a way to reset an already initialized component, and reconnecting the element does not reapply them. To reset state later, write the current property.
Synchronize application-owned state
Write the current property when the application owns the state, and listen for the component's event to capture user changes. This example starts with the disclosure markup above:
import '@rcarls/rc-webcomponents/rc-disclosure/define';
const $disclosure = document.querySelector('rc-disclosure');
let deliveryOpen = true;
function setDeliveryOpen(open) {
deliveryOpen = open;
$disclosure.open = deliveryOpen;
}
$disclosure.addEventListener('rc-disclosure-toggle', (event) => {
setDeliveryOpen(event.detail.open);
});
setDeliveryOpen(deliveryOpen);
// A later application action can close the disclosure without simulating user input.
setDeliveryOpen(false);
Framework bindings follow the same loop: bind the current property from application state, then update that state from the component event. Check the component's behavior when deciding whether to accept or restore a user-requested value; a property binding alone is not a universal veto mechanism.
Keep programmatic writes silent
Writing a current property from code does not dispatch the component's change event. Update your application state in the same action that writes the property; do not wait for an event that represents user interaction. This also keeps an event handler that writes state back to the element from creating a feedback loop.
rc-* events bubble and are composed, so an ancestor can listen across a shadow boundary.
Some imperative methods deliberately dispatch events; check their API documentation rather
than assuming they behave like property setters.
Update the component's current property
The native child keeps form submission and label association. When application code changes
an enhanced control, write the component's property so it can synchronize its rendered surface
and native child together. A direct write to the native child's JavaScript value or checked
property may not be observed by the wrapper.
Use JavaScript properties for arrays, objects, and boolean false. A boolean HTML attribute
is true when present: open="false" still supplies an open state. Multi-word attribute names
use kebab case, such as default-open; the JavaScript property is defaultOpen.
For framework-specific syntax, see React, Angular, Vue, Svelte, and Blazor.