Using Kobalte
KobalteKobalte's components in OCaml's JSX, styled by their own data attributes.
A Kobalte component is used as Kobalte’s documentation shows it, part by
part, in OCaml’s JSX. Kobalte has no look of its own: it writes its state
as data- attributes – data-checked, data-pressed, data-highlighted
– which a stylesheet or Tailwind’s data-[...]: variants style. The
examples below are styled with Tailwind on HeroUI’s colours.
Switch
Section titled “Switch”Off
module S = Solidtype.Solidmodule Jsx = Solidtype.Jsxmodule Switch = Solidtype_kobalte.Switch
let make () = let on, set_on = S.create_signal false in <div class_="flex flex-col items-start gap-3"> <Switch.Root class_="inline-flex items-center gap-3" checked=(fun () -> S.get on) on_change=(S.set set_on)> <Switch.Input class_="peer" /> <Switch.Control class_="inline-flex h-6 w-11 items-center rounded-full bg-default \ px-0.5 transition-colors data-[checked]:bg-accent \ peer-focus-visible:ring-2 peer-focus-visible:ring-focus"> <Switch.Thumb class_="size-5 rounded-full bg-white shadow transition-transform \ data-[checked]:translate-x-5" /> </Switch.Control> <Switch.Label>"Airplane mode"</Switch.Label> </Switch.Root> <p class_="text-sm text-muted"> (fun () -> Jsx.string (if S.get on then "On" else "Off")) </p> </div>The switch is controlled: checked is a thunk of a signal, and on_change
is given the new value, a bool, as Kobalte’s onChange is. Left out,
default_checked sets where an uncontrolled switch starts. Switch.Input
is the native checkbox Kobalte hides, which a form submits and focus
reaches, so the control’s focus ring is styled from it, peer-focus-visible.
Toggle group
Section titled “Toggle group”left; bold
module S = Solidtype.Solidmodule Jsx = Solidtype.Jsxmodule ToggleGroup = Solidtype_kobalte.ToggleGroup
let item = "rounded-lg px-3 py-1 text-sm text-foreground hover:bg-default \ data-[pressed]:bg-accent data-[pressed]:text-accent-foreground"
let make () = let align, set_align = S.create_signal (Js.Nullable.return "left") in let styles, set_styles = S.create_signal [| "bold" |] in <div class_="flex flex-col items-start gap-3"> <ToggleGroup.Root class_="flex gap-1" aria_label="Alignment" value=(fun () -> S.get align) on_change=(S.set set_align)> <ToggleGroup.Item class_=(fun () -> item) value="left"> "Left" </ToggleGroup.Item> <ToggleGroup.Item class_=(fun () -> item) value="center"> "Center" </ToggleGroup.Item> <ToggleGroup.Item class_=(fun () -> item) value="right"> "Right" </ToggleGroup.Item> </ToggleGroup.Root> <ToggleGroup.Root_multiple class_="flex gap-1" aria_label="Style" multiple=true value=(fun () -> S.get styles) on_change=(S.set set_styles)> <ToggleGroup.Item class_=(fun () -> item) value="bold"> "Bold" </ToggleGroup.Item> <ToggleGroup.Item class_=(fun () -> item) value="italic"> "Italic" </ToggleGroup.Item> </ToggleGroup.Root_multiple> <p class_="text-sm text-muted"> (fun () -> let align = Option.value (Js.Nullable.toOption (S.get align)) ~default:"none" in Jsx.string (align ^ "; " ^ String.concat ", " (Array.to_list (S.get styles)))) </p> </div>ToggleGroup.Root holds one value, or none: its value is a
string Js.Nullable.t, since Kobalte reads undefined as uncontrolled and
null as a controlled group with nothing pressed. ToggleGroup.Root_multiple
holds several, a string array, and takes multiple=true. Each has the
exact type of its value, where TypeScript has one component whose type
turns on multiple.
class_=(fun () -> item) gives each item a class held in a name. A name
alone is passed to a component as it is, which a component’s check takes
only for a function, so a constant is passed as the thunk it is read by.
Select
Section titled “Select”module S = Solidtype.Solidmodule Jsx = Solidtype.Jsxmodule K = Solidtype_kobaltemodule Select = K.Selectmodule Node = K.Types.CollectionNodemodule Item_props = K.Types.SelectBaseItemComponentPropsmodule Value_state = K.Types.SelectValueState
let make () = <Select.Root class_="flex w-56 flex-col gap-1" options=(fun () -> Array.map K.Types.Either.left [| "Apple"; "Banana"; "Cherry" |]) placeholder=(<span class_="text-muted">"Pick a fruit"</span>) item_component=(fun props -> <Select.Item class_="flex cursor-default items-center \ justify-between rounded-lg px-2 py-1 \ outline-none data-[highlighted]:bg-default" item=(fun () -> Item_props.item props)> <Select.ItemLabel> (fun () -> Jsx.string (Node.raw_value (Item_props.item props))) </Select.ItemLabel> <Select.ItemIndicator>{js|✓|js}</Select.ItemIndicator> </Select.Item>)> <Select.Label class_="text-sm">"Fruit"</Select.Label> <Select.Trigger class_="flex items-center justify-between rounded-xl border \ border-border bg-field px-3 py-2 text-left"> <Select.Value> (fun state -> <span> (fun () -> Jsx.string (S.get (Value_state.selected_option state))) </span>) </Select.Value> <Select.Icon>{js|▾|js}</Select.Icon> </Select.Trigger> <Select.Portal> <Select.Content class_="heroui-ground rounded-xl border border-border bg-overlay p-1 \ shadow-lg"> <Select.Listbox /> </Select.Content> </Select.Portal> </Select.Root>A select shows more of the binding:
optionsis an array of(string, Types.<group>) Either.t: Kobalte takes an option or a group of options, values of different JavaScript kinds, so each is wrapped byEither.leftorEither.right, which writes the value as it is.item_componentis a function prop, given Kobalte’s item props, aTypes.SelectBaseItemComponentProps.t, read by its getters:Item_props.item propsis the item’s collection node, andNode.raw_valuethe option it holds.Select.Value’s child is a function given the value’s state,Types.SelectValueState.t, whoseselected_optionis an accessor.Select.Portalmounts the list indocument.body, outside the page’s flow, as Kobalte’s own does; the list is given HeroUI’s ground there, since it no longer inherits the example’s.
Every component
Section titled “Every component”The binding holds every component in Kobalte’s documentation, from
Accordion to Tooltip, by the same rules. Each module’s .mli is the
reference for its parts and their props, in OCaml; Kobalte’s documentation
is the reference for what they do.
HeroUI is built on these components: each port is a Kobalte component with HeroUI’s classes, and its page shows it beside HeroUI’s own.
