Skip to main content
Solid Primitives 2

Reactive signals that support both controlled (externally managed) and uncontrolled (internally managed) state — a pattern commonly used in headless UI components.

StageCategoryVersionLast UpdatedDemo
3Reactivity1.0.0-next.3 (next)Aug 12, 2026Demo →
Terminal window
npm i @solid-primitives/controlled-signal@next

Reactive signals that support both controlled (externally managed) and uncontrolled (internally managed) state — a pattern commonly used in headless UI components such as dialogs, dropdowns, and toggles.

How it works

A controllable signal has two modes:

  • Uncontrolled — when no value prop is provided (or it is undefined). The signal manages its own internal state, initialized from defaultValue.
  • Controlled — when a value prop is provided. The signal defers entirely to the external value; setValue calls onChange but does not update internal state.

This mirrors the controlled/uncontrolled pattern from React, adapted for SolidJS reactivity.

Primitives

createControllableSignal

import { createControllableSignal } from "@solid-primitives/controlled-signal";
const [value, setValue] = createControllableSignal<T>(props);

Props:

PropTypeDescription
valueAccessor<T | undefined>Controlled value. When defined, enables controlled mode.
defaultValueAccessor<T | undefined>Initial value for uncontrolled mode.
onChange(value: T) => voidCalled whenever the value would change.

Returns: [value: Accessor<T | undefined>, setValue: (next) => void]

Uncontrolled usage

const [open, setOpen] = createControllableSignal({ defaultValue: () => false });
setOpen(true); // updates internal state and calls onChange if provided
setOpen(prev => !prev); // functional form

Controlled usage

// The consumer drives the state; your component just calls onChange.
const [value, setValue] = createControllableSignal({
value: () => props.open,
onChange: props.onOpenChange,
});
// setValue does NOT update internal state in controlled mode.
// It calls onChange so the consumer can update their signal.
setValue(true);

createControllableBooleanSignal

Variant of createControllableSignal for boolean values. Returns false instead of undefined when unset.

const [open, setOpen] = createControllableBooleanSignal({
defaultValue: () => false,
});

createControllableArraySignal

Variant for Array<T> values. Returns [] instead of undefined when unset.

const [items, setItems] = createControllableArraySignal<string>({
defaultValue: () => ["a", "b"],
});
setItems(prev => [...prev, "c"]);

createControllableSetSignal

Variant for Set<T> values. Returns new Set() instead of undefined when unset.

const [selected, setSelected] = createControllableSetSignal<number>({
defaultValue: () => new Set([1, 2]),
});
setSelected(prev => new Set([...prev, 3]));

createToggleState

Controllable state for toggle components — checkboxes, switches, toggle buttons — built on top of createControllableBooleanSignal. Adapted from Kobalte's createToggleState.

import { createToggleState } from "@solid-primitives/controlled-signal";
const { isSelected, setIsSelected, toggle } = createToggleState({
defaultIsSelected: () => false,
});
toggle(); // isSelected() === true

Props:

PropTypeDescription
isSelectedAccessor<boolean | undefined>Controlled selected state. When defined, enables controlled mode.
defaultIsSelectedAccessor<boolean | undefined>Initial selected state for uncontrolled mode.
isDisabledAccessor<boolean | undefined>While true, toggle() and setIsSelected() are no-ops.
isReadOnlyAccessor<boolean | undefined>While true, toggle() and setIsSelected() are no-ops.
onSelectedChange(isSelected: boolean) => voidCalled whenever the selected state would change.

Returns: { isSelected: Accessor<boolean>, setIsSelected: (v: boolean) => void, toggle: () => void }

Just like createControllableSignal, isSelected can be omitted for uncontrolled usage, or provided (alongside onSelectedChange) to let a parent component drive the state.

Credits

This primitive is adapted from the create-controllable-signal and create-toggle-state implementations in Kobalte by the Kobalte Contributors, used under the MIT License.

Changelog

See CHANGELOG.md

Solid Primitives 2High-quality reactive primitives for building applications in Solid2
Community
githubdiscord