Tabs nullable selection migration
Tabs selection is string | null: a string selects a tab, and null represents no selected tab. Keep parent state and change handlers wide enough to represent both.
Update controlled state
Section titled “Update controlled state”// Reactconst [value, setValue] = useState<string | null>('react')<Tabs.Root value={value} onValueChange={({ value }) => setValue(value)} />// Vueconst value = ref<string | null>('react')// Use value with <Tabs.Root v-model="value">.
// Solidconst [value, setValue] = createSignal<string | null>('react')
// Sveltelet value = $state<string | null>('react')// Use value with <Tabs.Root bind:value>.A callback that only accepts { value: string } cannot handle a clear request. Accept { value: string | null } and explicitly decide whether to accept or veto an empty selection. Do not use a non-null assertion to hide that decision.
function onValueChange(details: { value: string | null }) { setValue(details.value)}Vue’s public modelValue, defaultValue, valueChange and update:modelValue types also accept null. undefined continues to mean that no controlled value was supplied; use null for a controlled empty selection.
Core dependency boundary
Section titled “Core dependency boundary”The pinned @destyler/tabs@0.2.13 includes the core correction: clearValue() and deselectable user activation report { value: null }, and ValueChangeDetails.value is string | null. This includes clicking the selected trigger and pressing Enter on it in manual activation mode.
A controlled parent still owns the selection. Accept a clear request by writing null back, keep the current value to veto it, or apply the requested value later. Repeated clear requests while a non-null value remains controlled notify again; after the parent accepts null, another clearValue() is a no-op. Parent writes do not echo a value-change notification. Uncontrolled Tabs update their own selection and report the change.
setValue(value) continues to accept a string and select that tab, even with deselectable enabled. Calling it with the already selected string does not toggle the selection off. Use clearValue() to request an empty selection.
Consumers upgrading from 0.2.11 must handle these newly delivered null notifications. That version accepted parent-controlled null but suppressed core-initiated null change callbacks; widening a handler alone did not enable them.