State model
BeeUI components are state-transparent by default: a component holds internal state only for the interaction it owns, and any value your product cares about is passed in and reported back through a callback.
Prerequisites: read Composition model first — this page assumes you already know how a compound component’s root and parts share context.
Why the concept exists
Section titled “Why the concept exists”Controlled vs uncontrolled sounds like an API detail until it decides whether a user’s half-filled form survives a re-render. BeeUI does not apply one blanket policy, because the right answer differs by component: whether a checkbox is ticked is almost always product state, while whether a tooltip is showing almost never is.
So instead of guessing, learn the three shapes BeeUI actually uses. Every component falls into one of them, and the exact props for each are on its component reference page.
The three shapes
Section titled “The three shapes”shape 1 — always controlled shape 2 — dual-mode shape 3 — runtime-owned────────────────────────── ────────────────────────── ───────────────────────you hold the value pass the prop → you own it the provider runtimecomponent renders and reports omit it → it owns it owns the state; youno internal fallback `default*` seeds the uncontrolled drive it imperatively path at mount only
Checkbox checked/onCheckedChange selection state Toast useToast()Switch value/onValueChange Accordion value/defaultValue show / dismissTabs value/onValueChange ChipGroup value/defaultValue dismissAllRadioGroup value/onValueChange Select value/defaultValueSegmentedControl value/onValueChangeInput, Textarea open state (React Native TextInput semantics) Dialog · AlertDialog · Sheet · Popover DropdownMenu · Tooltip · Collapsible · Select all use open/defaultOpen/onOpenChangeShape 1 components have no internal fallback: they render exactly what you pass. Shape 2 components decide per prop — pass value and you own selection, omit it and they own it — and every overlay asks the same question separately for open. Select appears in both selection and open state because those two decisions are independent: you can control the value and let the surface manage its own open state.
Rules and invariants
Section titled “Rules and invariants”- A controlled prop needs its callback. Pass
value(orchecked, oropen) without the matchingonValueChange/onCheckedChange/onOpenChangeand the control becomes read-only: it renders your value and can never change it. BeeUI warns about this in development builds rather than failing silently in production. - Do not switch modes at runtime. Going from
undefinedto a definedvalueon an already-mounted component moves ownership mid-flight. Decide once per usage site. defaultValueis a seed, not a sync. It is read when the component mounts. Changing it later does nothing; remounting is the only thing that re-reads it — which is why a conditionally rendered root silently resets.- The overlay families enforce the pairing in the type system.
Dialog,Popover,DropdownMenu,SheetandTooltiptype their props as a union: supplyopenandonOpenChangebecomes required whiledefaultOpenis rejected; supply neither anddefaultOpenis available. A controlled dialog that cannot close is therefore a compile error rather than a bug report. - Uncontrolled state does not survive unmount. If a navigation transition unmounts the screen, uncontrolled selection is gone. That is a correct outcome for a disclosure and a bug for a shopping cart.
- Overlay dismissal is runtime-arbitrated, not prop-arbitrated. Even a controlled
openstill participates in the overlay runtime’s depth-based dismissal; your callback is how you find out. See Overlays & runtime ownership.
Consequences for application code
Section titled “Consequences for application code”The decision rule is short: if the value has meaning outside the component, control it.
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@beemvp/beeui-ui';
// Uncontrolled: which FAQ entry is open is a local interaction detail.export function Faq() { return ( <Accordion defaultValue="shipping"> <AccordionItem value="shipping"> <AccordionTrigger>Shipping</AccordionTrigger> <AccordionContent>Ships in two business days.</AccordionContent> </AccordionItem> </Accordion> );}import * as React from 'react';import { Checkbox, Field } from '@beemvp/beeui-ui';
// Controlled: consent is submitted, validated and audited — it is product state.export function ConsentField() { const [accepted, setAccepted] = React.useState(false); return ( <Field label="Terms"> <Checkbox checked={accepted} onCheckedChange={setAccepted} /> </Field> );}Practical consequences worth planning around:
- Form values are always yours. BeeUI ships no form engine, so the values, the dirty flags and the submit lifecycle live in your state or your form library. See Forms model.
- Derived UI state usually is not. A tooltip, a hover state, a disclosure inside a card — leave them alone; controlling them adds re-renders and gains nothing.
- Controlled overlays are for cross-cutting rules. Control
openwhen routing, a mutation result or a guard needs to close a dialog. OtherwiseDialogTriggeris less code and fewer states to get wrong.
Common misconception
Section titled “Common misconception”“Controlled is the safe default, so I will control everything.”
Controlling everything means every keystroke and every hover routes through your state tree. You pay in re-renders and in state you must now keep correct, and you gain nothing for values nobody outside the component reads. The mirror-image anti-pattern is worse: keeping a useState copy alongside an uncontrolled component and syncing it in an effect. That is two sources of truth that will disagree — pick one owner.
Where to go next
Section titled “Where to go next”- Composition model — the root that owns this state.
- Forms model — validation and submit ownership.
- Overlays & runtime ownership — open state and dismissal arbitration.
- Components — the exact prop names and types per component.
- Table guide and Date & time guide — two surfaces where state ownership matters most.
Source authority
Section titled “Source authority”packages/ui/src/index.ts— the exported prop types for every component.docs/components.md— the canonical behavior catalog, including controlled/uncontrolled defaults.