On this pageOverview
Switch
An on/off toggle. Semantically different from Checkbox: Switch represents an immediate action (like a light switch), while Checkbox represents a form value that gets submitted. Switch uses the Submodel pattern with the same wiring as Checkbox.
See it in an app
Check out how Switch is wired up in a real Foldkit app.
The switch renders as a <button> with role="switch". The typical visual is a track with a sliding knob, styled with the data-checked attribute for the on state.
Get notified when something important happens.
// Pseudocode walkthrough of the Foldkit integration points. Each labeled
// block below is an excerpt. Fit them into your own Model, init, Message,
// update, and view definitions.
import { Match as M, Option } from 'effect'
import { Command, Ui } from 'foldkit'
import { html } from 'foldkit/html'
import { m } from 'foldkit/message'
import { evo } from 'foldkit/struct'
// Add a field to your Model for the Switch Submodel:
const Model = S.Struct({
switchDemo: Ui.Switch.Model,
// ...your other fields
})
// In your init function, initialize the Switch Submodel with a unique id:
const init = () => [
{
switchDemo: Ui.Switch.init({ id: 'notifications' }),
// ...your other fields
},
[],
]
// Embed the Switch Message in your parent Message:
const GotSwitchMessage = m('GotSwitchMessage', {
message: Ui.Switch.Message,
})
// Inside your update function's M.tagsExhaustive({...}), delegate to
// Ui.Switch.update. The OutMessage's `ToggledChecked` carries the new
// `isChecked` value. Use it to save a preference, sync to a backend,
// or trigger a side effect at the toggle moment.
GotSwitchMessage: ({ message }) => {
const [nextSwitch, commands, maybeOutMessage] = Ui.Switch.update(
model.switchDemo,
message,
)
const mappedCommands = Command.mapMessages(commands, message =>
GotSwitchMessage({ message }),
)
return Option.match(maybeOutMessage, {
onNone: () => [
evo(model, { switchDemo: () => nextSwitch }),
mappedCommands,
],
onSome: M.type<Ui.Switch.OutMessage>().pipe(
M.tagsExhaustive({
ToggledChecked: ({ isChecked }) => {
// The child has emitted `ToggledChecked`. The body commits
// the child's next state as usual. In this arm the parent
// can also update its own state or dispatch its own
// Commands, for example persist the preference, fire
// analytics, or dispatch a downstream Command.
return [evo(model, { switchDemo: () => nextSwitch }), mappedCommands]
},
}),
),
})
}
// Inside your view function, embed the Switch via h.submodel:
const view = () => {
const h = html<Message>()
return h.submodel({
slotId: 'switch-demo',
model: model.switchDemo,
view: Ui.Switch.view,
viewInputs: {
toView: attributes =>
h.div(
[h.Class('flex items-center gap-3')],
[
h.button(
[
...attributes.button,
h.Class(
'relative h-6 w-11 rounded-full transition-colors data-[checked]:bg-blue-600 bg-gray-200',
),
],
[
h.div(
[
h.Class(
'absolute top-0.5 left-0.5 h-5 w-5 rounded-full bg-white transition-transform',
),
],
[],
),
],
),
h.div(
[],
[
h.label(
[...attributes.label, h.Class('text-sm font-medium')],
['Enable notifications'],
),
h.p(
[...attributes.description, h.Class('text-sm text-gray-500')],
['Get notified when something important happens.'],
),
],
),
],
),
},
toParentMessage: message => GotSwitchMessage({ message }),
})
}Switch is headless. Your toView callback controls all markup and styling. Use data-[checked] to change the track color and translate the knob.
| Attribute | Condition |
|---|---|
data-checked | Present when the switch is on. |
data-disabled | Present when isDisabled is true. |
| Key | Description |
|---|---|
| Space | Toggles the switch. |
The switch button receives role="switch" and aria-checked. The label is linked via aria-labelledby and the description via aria-describedby. Clicking the label toggles the switch.
Configuration object passed to Switch.init().
| Name | Type | Default | Description |
|---|---|---|---|
id | string | - | Unique ID for the switch instance. |
isChecked | boolean | false | Initial on/off state. |
Configuration object passed to Switch.view().
| Name | Type | Default | Description |
|---|---|---|---|
model | Switch.Model | - | The switch state from your parent Model. |
toParentMessage | (childMessage: Switch.Message) => ParentMessage | - | Wraps Switch Messages in your parent Message type for Submodel delegation. |
toView | (attributes: SwitchAttributes) => Html | - | Callback that receives attribute groups for the button, label, description, and hidden input elements. |
isDisabled | boolean | false | Whether the switch is disabled. |
name | string | - | Form field name. When provided, a hidden input is included for native form submission. |
value | string | 'on' | Value sent in the form when checked. |
Attribute groups provided to the toView callback.
| Name | Type | Default | Description |
|---|---|---|---|
button | ReadonlyArray<Attribute<Message>> | - | Spread onto the switch button element. Includes role, aria-checked, tabindex, and click/keyboard handlers. |
label | ReadonlyArray<Attribute<Message>> | - | Spread onto the label element. Includes an id for aria-labelledby and a click handler that toggles the switch. |
description | ReadonlyArray<Attribute<Message>> | - | Spread onto a description element. Includes an id referenced by aria-describedby on the switch. |
hiddenInput | ReadonlyArray<Attribute<Message>> | - | Spread onto a hidden <input> for form submission. Only needed when the name prop is set. |
Messages emitted to the parent through the third element of [Model, Commands, Option<OutMessage>]. Pattern-match on the OutMessage in your update handler.
| Name | Type | Default | Description |
|---|---|---|---|
ToggledChecked | { isChecked: boolean } | - | Emitted each time the switch toggles. Carries the new checked state. Pattern-match the third tuple element of Switch.update in your GotSwitchMessage handler to lift the toggle into a domain Message (e.g., persisting the setting or dispatching a sync command). |