ComboBox
HeroUIAn input that filters a list of options to choose one or many of, HeroUI's on Kobalte's.
Solidtype_heroui.Combo_box is Kobalte’s Combobox, with the classes
HeroUI’s own comboBoxVariants gives. Its input is the Input inside its
input group, and its list the List_box inside its popover, as in HeroUI;
Label, Description and Field_error inside it name, describe and
qualify it.
module Combo_box = Solidtype_heroui.Combo_boxmodule Input = Solidtype_heroui.Inputmodule Label = Solidtype_heroui.Labelmodule List_box = Solidtype_heroui.List_box
let make () = <Combo_box class_="w-[256px]"> <Label>"Favorite Animal"</Label> <Combo_box.Input_group> <Input placeholder="Search animals..." /> <Combo_box.Trigger /> </Combo_box.Input_group> <Combo_box.Popover> <List_box> <List_box.Item id="aardvark" text_value="Aardvark"> "Aardvark" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="cat" text_value="Cat"> "Cat" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="dog" text_value="Dog"> "Dog" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="kangaroo" text_value="Kangaroo"> "Kangaroo" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="panda" text_value="Panda"> "Panda" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="snake" text_value="Snake"> "Snake" <List_box.Item_indicator /> </List_box.Item> </List_box> </Combo_box.Popover> </Combo_box>Anatomy
Section titled “Anatomy”<Combo_box> <Label>...</Label> <Combo_box.Input_group> <Input /> <Combo_box.Trigger /> </Combo_box.Input_group> <Combo_box.Value /> <Description>...</Description> <Combo_box.Popover> <List_box> <List_box.Item id="...">...</List_box.Item> </List_box> </Combo_box.Popover></Combo_box>Examples
Section titled “Examples”Full Width
Section titled “Full Width”module Combo_box = Solidtype_heroui.Combo_boxmodule Input = Solidtype_heroui.Inputmodule Label = Solidtype_heroui.Labelmodule List_box = Solidtype_heroui.List_box
let make () = <div class_="w-[400px] space-y-4"> <Combo_box full_width=true> <Label>"Favorite Animal"</Label> <Combo_box.Input_group> <Input placeholder="Search animals..." /> <Combo_box.Trigger /> </Combo_box.Input_group> <Combo_box.Popover> <List_box> <List_box.Item id="aardvark" text_value="Aardvark"> "Aardvark" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="cat" text_value="Cat"> "Cat" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="dog" text_value="Dog"> "Dog" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="kangaroo" text_value="Kangaroo"> "Kangaroo" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="panda" text_value="Panda"> "Panda" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="snake" text_value="Snake"> "Snake" <List_box.Item_indicator /> </List_box.Item> </List_box> </Combo_box.Popover> </Combo_box> </div>With Description
Section titled “With Description”module Combo_box = Solidtype_heroui.Combo_boxmodule Input = Solidtype_heroui.Inputmodule Label = Solidtype_heroui.Labelmodule List_box = Solidtype_heroui.List_boxmodule Description = Solidtype_heroui.Description
let make () = <Combo_box class_="w-[256px]"> <Label>"Favorite Animal"</Label> <Combo_box.Input_group> <Input placeholder="Search animals..." /> <Combo_box.Trigger /> </Combo_box.Input_group> <Combo_box.Popover> <List_box> <List_box.Item id="aardvark" text_value="Aardvark"> "Aardvark" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="cat" text_value="Cat"> "Cat" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="dog" text_value="Dog"> "Dog" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="kangaroo" text_value="Kangaroo"> "Kangaroo" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="panda" text_value="Panda"> "Panda" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="snake" text_value="Snake"> "Snake" <List_box.Item_indicator /> </List_box.Item> </List_box> </Combo_box.Popover> <Description>"Search and select your favorite animal"</Description> </Combo_box>Required
Section titled “Required”HeroUI’s required combo box is checked by the browser as its form is
submitted. A Form here checks nothing itself, as with a text field: the
example says the combo box is invalid once a submit finds it empty.
module S = Solidtype.Solidmodule Button = Solidtype_heroui.Buttonmodule Combo_box = Solidtype_heroui.Combo_boxmodule Field_error = Solidtype_heroui.Field_errormodule Form = Solidtype_heroui.Formmodule Input = Solidtype_heroui.Inputmodule Label = Solidtype_heroui.Labelmodule List_box = Solidtype_heroui.List_box
external prevent_default : Dom.event -> unit = "preventDefault" [@@mel.send]external alert : string -> unit = "alert" [@@mel.scope "window"]
let make () = let animal, set_animal = S.create_signal [||] in (* A required combo box shows its error once a submit has been tried, as the browser's validation React Aria reads does. *) let tried, set_tried = S.create_signal false in let missing () = S.get tried && Array.length (S.get animal) = 0 in <Form class_="flex w-[256px] flex-col gap-4" on_submit=(fun e -> prevent_default e; S.set set_tried true; if Array.length (S.get animal) > 0 then alert "Form submitted successfully!")> <Combo_box is_required=true is_invalid=(fun () -> missing ()) class_="w-full" name="animal" on_change=(S.set set_animal)> <Label>"Favorite Animal"</Label> <Combo_box.Input_group> <Input placeholder="Search animals..." /> <Combo_box.Trigger /> </Combo_box.Input_group> <Combo_box.Popover> <List_box> <List_box.Item id="aardvark" text_value="Aardvark"> "Aardvark" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="cat" text_value="Cat"> "Cat" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="dog" text_value="Dog"> "Dog" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="kangaroo" text_value="Kangaroo"> "Kangaroo" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="panda" text_value="Panda"> "Panda" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="snake" text_value="Snake"> "Snake" <List_box.Item_indicator /> </List_box.Item> </List_box> </Combo_box.Popover> <Field_error>"Please select an item in the list."</Field_error> </Combo_box> <Button type_=`submit>"Submit"</Button> </Form>Disabled
Section titled “Disabled”module Combo_box = Solidtype_heroui.Combo_boxmodule Input = Solidtype_heroui.Inputmodule Label = Solidtype_heroui.Labelmodule List_box = Solidtype_heroui.List_box
let make () = <Combo_box is_disabled=true class_="w-[256px]" default_value=(fun () -> [| "cat" |])> <Label>"Favorite Animal"</Label> <Combo_box.Input_group> <Input placeholder="Search animals..." /> <Combo_box.Trigger /> </Combo_box.Input_group> <Combo_box.Popover> <List_box> <List_box.Item id="aardvark" text_value="Aardvark"> "Aardvark" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="cat" text_value="Cat"> "Cat" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="dog" text_value="Dog"> "Dog" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="kangaroo" text_value="Kangaroo"> "Kangaroo" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="panda" text_value="Panda"> "Panda" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="snake" text_value="Snake"> "Snake" <List_box.Item_indicator /> </List_box.Item> </List_box> </Combo_box.Popover> </Combo_box>With Disabled Options
Section titled “With Disabled Options”module Combo_box = Solidtype_heroui.Combo_boxmodule Input = Solidtype_heroui.Inputmodule Label = Solidtype_heroui.Labelmodule List_box = Solidtype_heroui.List_box
let make () = <Combo_box class_="w-[256px]" disabled_keys=(fun () -> [| "cat"; "kangaroo" |])> <Label>"Animal"</Label> <Combo_box.Input_group> <Input placeholder="Search animals..." /> <Combo_box.Trigger /> </Combo_box.Input_group> <Combo_box.Popover> <List_box> <List_box.Item id="dog" text_value="Dog"> "Dog" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="cat" text_value="Cat"> "Cat" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="bird" text_value="Bird"> "Bird" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="kangaroo" text_value="Kangaroo"> "Kangaroo" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="elephant" text_value="Elephant"> "Elephant" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="tiger" text_value="Tiger"> "Tiger" <List_box.Item_indicator /> </List_box.Item> </List_box> </Combo_box.Popover> </Combo_box>With Sections
Section titled “With Sections”A section the input’s text leaves empty is not drawn, its header with it.
module Combo_box = Solidtype_heroui.Combo_boxmodule Input = Solidtype_heroui.Inputmodule Label = Solidtype_heroui.Labelmodule List_box = Solidtype_heroui.List_boxmodule Header = Solidtype_heroui.Headermodule Separator = Solidtype_heroui.Separator
let make () = <Combo_box class_="w-[256px]"> <Label>"Country"</Label> <Combo_box.Input_group> <Input placeholder="Search countries..." /> <Combo_box.Trigger /> </Combo_box.Input_group> <Combo_box.Popover> <List_box> <List_box.Section> <Header>"North America"</Header> <List_box.Item id="usa" text_value="United States"> "United States" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="canada" text_value="Canada"> "Canada" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="mexico" text_value="Mexico"> "Mexico" <List_box.Item_indicator /> </List_box.Item> </List_box.Section> <Separator /> <List_box.Section> <Header>"Europe"</Header> <List_box.Item id="uk" text_value="United Kingdom"> "United Kingdom" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="france" text_value="France"> "France" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="germany" text_value="Germany"> "Germany" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="spain" text_value="Spain"> "Spain" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="italy" text_value="Italy"> "Italy" <List_box.Item_indicator /> </List_box.Item> </List_box.Section> <Separator /> <List_box.Section> <Header>"Asia"</Header> <List_box.Item id="japan" text_value="Japan"> "Japan" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="china" text_value="China"> "China" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="india" text_value="India"> "India" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="south-korea" text_value="South Korea"> "South Korea" <List_box.Item_indicator /> </List_box.Item> </List_box.Section> </List_box> </Combo_box.Popover> </Combo_box>Controlled
Section titled “Controlled”The ids chosen are value, an array, as a select’s are: one at most in a
choice of one, where HeroUI’s React component also takes selectedKey.
Selected: Cat
module S = Solidtype.Solidmodule Jsx = Solidtype.Jsxmodule Combo_box = Solidtype_heroui.Combo_boxmodule Input = Solidtype_heroui.Inputmodule Label = Solidtype_heroui.Labelmodule List_box = Solidtype_heroui.List_box
type animal = { id : string; name : string }
let animals = [| { id = "cat"; name = "Cat" }; { id = "dog"; name = "Dog" }; { id = "bird"; name = "Bird" }; { id = "fish"; name = "Fish" }; { id = "hamster"; name = "Hamster" }; |]
let make () = let selected, set_selected = S.create_signal [| "cat" |] in let selected_animal () = match S.get selected with | [| key |] -> Array.find_opt (fun a -> String.equal a.id key) animals | _ -> None in <div class_="space-y-2"> <Combo_box class_="w-[256px]" value=(fun () -> S.get selected) on_change=(S.set set_selected)> <Label>"Animal (controlled)"</Label> <Combo_box.Input_group> <Input placeholder="Search animals..." /> <Combo_box.Trigger /> </Combo_box.Input_group> <Combo_box.Popover> <List_box> <Solidtype.For each=(fun () -> animals)> (fun animal _ -> <List_box.Item id=(fun () -> animal.id) text_value=(fun () -> animal.name)> (fun () -> Jsx.string animal.name) <List_box.Item_indicator /> </List_box.Item>) </Solidtype.For> </List_box> </Combo_box.Popover> </Combo_box> <p class_="text-sm text-muted"> (fun () -> Jsx.string ("Selected: " ^ match selected_animal () with Some a -> a.name | None -> "None")) </p> </div>Controlled Input Value
Section titled “Controlled Input Value”Input value: (empty)
module S = Solidtype.Solidmodule Jsx = Solidtype.Jsxmodule Combo_box = Solidtype_heroui.Combo_boxmodule Input = Solidtype_heroui.Inputmodule Label = Solidtype_heroui.Labelmodule List_box = Solidtype_heroui.List_box
let make () = let input_value, set_input_value = S.create_signal "" in <div class_="space-y-2"> <Combo_box class_="w-[256px]" input_value=(fun () -> S.get input_value) on_input_change=(S.set set_input_value)> <Label>"Search (controlled input)"</Label> <Combo_box.Input_group> <Input placeholder="Type to search..." /> <Combo_box.Trigger /> </Combo_box.Input_group> <Combo_box.Popover> <List_box> <List_box.Item id="aardvark" text_value="Aardvark"> "Aardvark" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="cat" text_value="Cat"> "Cat" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="dog" text_value="Dog"> "Dog" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="kangaroo" text_value="Kangaroo"> "Kangaroo" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="panda" text_value="Panda"> "Panda" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="snake" text_value="Snake"> "Snake" <List_box.Item_indicator /> </List_box.Item> </List_box> </Combo_box.Popover> </Combo_box> <p class_="text-sm text-muted"> (fun () -> Jsx.string ("Input value: " ^ match S.get input_value with "" -> "(empty)" | text -> text)) </p> </div>Asynchronous Loading
Section titled “Asynchronous Loading”HeroUI’s example searches a server for each text typed; here the same names
are searched and loaded a part at a time. The list’s
List_box.Load_more_item asks for more as the list scrolls to its end.
HeroUI’s list also draws an empty state while it holds nothing, which a
List_box does not yet.
module S = Solidtype.Solidmodule Jsx = Solidtype.Jsxmodule Combo_box = Solidtype_heroui.Combo_boxmodule Input = Solidtype_heroui.Inputmodule Label = Solidtype_heroui.Labelmodule List_box = Solidtype_heroui.List_boxmodule Spinner = Solidtype_heroui.Spinner
(* The first two pages of https://swapi.py4e.com/api/people/, which HeroUI's example searches, here searched and loaded a part at a time. *)let characters = [| "Luke Skywalker"; "C-3PO"; "R2-D2"; "Darth Vader"; "Leia Organa"; "Owen Lars"; "Beru Whitesun lars"; "R5-D4"; "Biggs Darklighter"; "Obi-Wan Kenobi"; "Anakin Skywalker"; "Wilhuff Tarkin"; "Chewbacca"; "Han Solo"; "Greedo"; "Jabba Desilijic Tiure"; "Wedge Antilles"; "Jek Tono Porkins"; "Yoda"; "Palpatine"; |]
(* How long the fetch it stands in for takes, and how many each brings. *)let load_ms = 600let page = 10
(* Whether [part] is in [name], whatever their case, as the search finds it. *)let holds ~part name = let name = String.lowercase_ascii name and part = String.lowercase_ascii part in let n = String.length part in let rec from i = i + n <= String.length name && (String.equal (String.sub name i n) part || from (i + 1)) in from 0
let make () = let filter_text, set_filter_text = S.create_signal "" in let loaded, set_loaded = S.create_signal 0 in let loading, set_loading = S.create_signal false in let found () = Array.of_list (List.filter (fun name -> holds ~part:(S.get filter_text) name) (Array.to_list characters)) in let load_more () = let now = S.get loaded in if (not (S.get loading)) && now < Array.length (found ()) then ( S.set set_loading true; let timer = Js.Global.setTimeout ~f:(fun () -> S.set set_loaded (now + page); S.set set_loading false) load_ms in S.on_cleanup (fun () -> Js.Global.clearTimeout timer)) in let search text = S.set set_filter_text text; S.set set_loaded 0; load_more () in S.on_mount load_more; <Combo_box allows_empty_collection=true class_="w-[256px]" input_value=(fun () -> S.get filter_text) on_input_change=search> <Label>"Pick a Character"</Label> <Combo_box.Input_group> <Input placeholder="Star Wars characters..." /> <Combo_box.Trigger /> </Combo_box.Input_group> <Combo_box.Popover> <List_box> <Solidtype.For each=(fun () -> let found = found () in Array.sub found 0 (min (S.get loaded) (Array.length found)))> (fun name _ -> <List_box.Item id=(fun () -> name) text_value=(fun () -> name)> (fun () -> Jsx.string name) <List_box.Item_indicator /> </List_box.Item>) </Solidtype.For> <List_box.Load_more_item is_loading=(fun () -> S.get loading) on_load_more=load_more> <div class_="flex items-center justify-center gap-2 py-2"> <Spinner size=`sm /> <span class_="muted text-sm">"Loading more..."</span> </div> </List_box.Load_more_item> </List_box> </Combo_box.Popover> </Combo_box>Default Selected Key
Section titled “Default Selected Key”module Combo_box = Solidtype_heroui.Combo_boxmodule Input = Solidtype_heroui.Inputmodule Label = Solidtype_heroui.Labelmodule List_box = Solidtype_heroui.List_box
let make () = <Combo_box class_="w-[256px]" default_value=(fun () -> [| "cat" |])> <Label>"Favorite Animal"</Label> <Combo_box.Input_group> <Input placeholder="Search animals..." /> <Combo_box.Trigger /> </Combo_box.Input_group> <Combo_box.Popover> <List_box> <List_box.Item id="aardvark" text_value="Aardvark"> "Aardvark" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="cat" text_value="Cat"> "Cat" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="dog" text_value="Dog"> "Dog" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="kangaroo" text_value="Kangaroo"> "Kangaroo" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="panda" text_value="Panda"> "Panda" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="snake" text_value="Snake"> "Snake" <List_box.Item_indicator /> </List_box.Item> </List_box> </Combo_box.Popover> </Combo_box>Allows Custom Value
Section titled “Allows Custom Value”module Combo_box = Solidtype_heroui.Combo_boxmodule Input = Solidtype_heroui.Inputmodule Label = Solidtype_heroui.Labelmodule List_box = Solidtype_heroui.List_boxmodule Description = Solidtype_heroui.Description
let make () = <Combo_box allows_custom_value=true class_="w-[256px]"> <Label>"Favorite Animal"</Label> <Combo_box.Input_group> <Input placeholder="Search or type an animal..." /> <Combo_box.Trigger /> </Combo_box.Input_group> <Combo_box.Popover> <List_box> <List_box.Item id="aardvark" text_value="Aardvark"> "Aardvark" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="cat" text_value="Cat"> "Cat" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="dog" text_value="Dog"> "Dog" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="kangaroo" text_value="Kangaroo"> "Kangaroo" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="panda" text_value="Panda"> "Panda" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="snake" text_value="Snake"> "Snake" <List_box.Item_indicator /> </List_box.Item> </List_box> </Combo_box.Popover> <Description> "You can type any animal name, even if it's not in the list" </Description> </Combo_box>Custom Indicator
Section titled “Custom Indicator”module Combo_box = Solidtype_heroui.Combo_boxmodule Input = Solidtype_heroui.Inputmodule Label = Solidtype_heroui.Labelmodule List_box = Solidtype_heroui.List_box
let make () = <Combo_box class_="w-[256px]"> <Label>"Favorite Animal"</Label> <Combo_box.Input_group> <Input placeholder="Search animals..." /> <Combo_box.Trigger> <Gravity.Chevrons_expand_vertical class_="size-3" /> </Combo_box.Trigger> </Combo_box.Input_group> <Combo_box.Popover> <List_box> <List_box.Item id="aardvark" text_value="Aardvark"> "Aardvark" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="cat" text_value="Cat"> "Cat" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="dog" text_value="Dog"> "Dog" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="kangaroo" text_value="Kangaroo"> "Kangaroo" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="panda" text_value="Panda"> "Panda" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="snake" text_value="Snake"> "Snake" <List_box.Item_indicator /> </List_box.Item> </List_box> </Combo_box.Popover> </Combo_box>Custom Value
Section titled “Custom Value”module Jsx = Solidtype.Jsxmodule Avatar = Solidtype_heroui.Avatarmodule Combo_box = Solidtype_heroui.Combo_boxmodule Description = Solidtype_heroui.Descriptionmodule Input = Solidtype_heroui.Inputmodule Label = Solidtype_heroui.Labelmodule List_box = Solidtype_heroui.List_box
type user = { avatar_url : string; email : string; fallback : string; id : string; name : string;}
let users = [| { avatar_url = "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg"; email = "bob@heroui.com"; fallback = "B"; id = "1"; name = "Bob"; }; { avatar_url = "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg"; email = "fred@heroui.com"; fallback = "F"; id = "2"; name = "Fred"; }; { avatar_url = "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg"; email = "martha@heroui.com"; fallback = "M"; id = "3"; name = "Martha"; }; { avatar_url = "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg"; email = "john@heroui.com"; fallback = "J"; id = "4"; name = "John"; }; { avatar_url = "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg"; email = "jane@heroui.com"; fallback = "J"; id = "5"; name = "Jane"; }; |]
let make () = <Combo_box class_="w-[256px]"> <Label>"User"</Label> <Combo_box.Input_group> <Input placeholder="Search users..." /> <Combo_box.Trigger /> </Combo_box.Input_group> <Combo_box.Popover> <List_box> <Solidtype.For each=(fun () -> users)> (fun (user : user) _ -> <List_box.Item id=(fun () -> user.id) text_value=(fun () -> user.name)> <Avatar size=`sm> <Avatar.Image src=(fun () -> user.avatar_url) /> <Avatar.Fallback> (fun () -> Jsx.string user.fallback) </Avatar.Fallback> </Avatar> <div class_="flex flex-col"> <Label>(fun () -> Jsx.string user.name)</Label> <Description>(fun () -> Jsx.string user.email)</Description> </div> <List_box.Item_indicator /> </List_box.Item>) </Solidtype.For> </List_box> </Combo_box.Popover> </Combo_box>Custom Filtering
Section titled “Custom Filtering”default_filter is given an option’s text and the input’s, and keeps the
options it answers true for.
module Jsx = Solidtype.Jsxmodule Combo_box = Solidtype_heroui.Combo_boxmodule Input = Solidtype_heroui.Inputmodule Label = Solidtype_heroui.Labelmodule List_box = Solidtype_heroui.List_box
type animal = { id : string; name : string }
let animals = [| { id = "cat"; name = "Cat" }; { id = "dog"; name = "Dog" }; { id = "bird"; name = "Bird" }; { id = "fish"; name = "Fish" }; { id = "hamster"; name = "Hamster" }; |]
(* Whether [part] is in [text], whatever their case. *)let holds ~part text = let text = String.lowercase_ascii text and part = String.lowercase_ascii part in let n = String.length part in let rec from i = i + n <= String.length text && (String.equal (String.sub text i n) part || from (i + 1)) in from 0
let make () = <Combo_box class_="w-[256px]" default_filter=(fun text typed -> String.equal typed "" || holds ~part:typed text)> <Label>"Animal (custom filter)"</Label> <Combo_box.Input_group> <Input placeholder="Search animals..." /> <Combo_box.Trigger /> </Combo_box.Input_group> <Combo_box.Popover> <List_box> <Solidtype.For each=(fun () -> animals)> (fun animal _ -> <List_box.Item id=(fun () -> animal.id) text_value=(fun () -> animal.name)> (fun () -> Jsx.string animal.name) <List_box.Item_indicator /> </List_box.Item>) </Solidtype.For> </List_box> </Combo_box.Popover> </Combo_box>Render Function
Section titled “Render Function”HeroUI’s Render Function example gives the combo box render, React’s way
to swap the element it renders, which a Solid component has no need of.
Menu Trigger
Section titled “Menu Trigger”Focus (default)
Input
Manual
module Combo_box = Solidtype_heroui.Combo_boxmodule Input = Solidtype_heroui.Inputmodule Label = Solidtype_heroui.Labelmodule List_box = Solidtype_heroui.List_boxmodule Description = Solidtype_heroui.Description
let make () = <div class_="flex flex-col gap-8"> <div class_="flex flex-col gap-2"> <p class_="text-sm font-medium text-muted">"Focus (default)"</p> <Combo_box class_="w-[256px]" menu_trigger=`focus> <Label>"Favorite Animal"</Label> <Combo_box.Input_group> <Input placeholder="Search animals..." /> <Combo_box.Trigger /> </Combo_box.Input_group> <Combo_box.Popover> <List_box> <List_box.Item id="aardvark" text_value="Aardvark"> "Aardvark" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="cat" text_value="Cat"> "Cat" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="dog" text_value="Dog"> "Dog" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="kangaroo" text_value="Kangaroo"> "Kangaroo" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="panda" text_value="Panda"> "Panda" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="snake" text_value="Snake"> "Snake" <List_box.Item_indicator /> </List_box.Item> </List_box> </Combo_box.Popover> <Description>"Popover opens when the input is focused"</Description> </Combo_box> </div> <div class_="flex flex-col gap-2"> <p class_="text-sm font-medium text-muted">"Input"</p> <Combo_box class_="w-[256px]" menu_trigger=`input> <Label>"Favorite Animal"</Label> <Combo_box.Input_group> <Input placeholder="Search animals..." /> <Combo_box.Trigger /> </Combo_box.Input_group> <Combo_box.Popover> <List_box> <List_box.Item id="aardvark" text_value="Aardvark"> "Aardvark" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="cat" text_value="Cat"> "Cat" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="dog" text_value="Dog"> "Dog" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="kangaroo" text_value="Kangaroo"> "Kangaroo" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="panda" text_value="Panda"> "Panda" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="snake" text_value="Snake"> "Snake" <List_box.Item_indicator /> </List_box.Item> </List_box> </Combo_box.Popover> <Description> "Popover opens when the user edits the input text" </Description> </Combo_box> </div> <div class_="flex flex-col gap-2"> <p class_="text-sm font-medium text-muted">"Manual"</p> <Combo_box class_="w-[256px]" menu_trigger=`manual> <Label>"Favorite Animal"</Label> <Combo_box.Input_group> <Input placeholder="Search animals..." /> <Combo_box.Trigger /> </Combo_box.Input_group> <Combo_box.Popover> <List_box> <List_box.Item id="aardvark" text_value="Aardvark"> "Aardvark" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="cat" text_value="Cat"> "Cat" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="dog" text_value="Dog"> "Dog" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="kangaroo" text_value="Kangaroo"> "Kangaroo" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="panda" text_value="Panda"> "Panda" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="snake" text_value="Snake"> "Snake" <List_box.Item_indicator /> </List_box.Item> </List_box> </Combo_box.Popover> <Description> "Popover only opens when the trigger button is pressed or arrow keys \ are used" </Description> </Combo_box> </div> </div>Form Value
Section titled “Form Value”module S = Solidtype.Solidmodule Jsx = Solidtype.Jsxmodule Button = Solidtype_heroui.Buttonmodule Combo_box = Solidtype_heroui.Combo_boxmodule Description = Solidtype_heroui.Descriptionmodule Field_error = Solidtype_heroui.Field_errormodule Form = Solidtype_heroui.Formmodule Input = Solidtype_heroui.Inputmodule Label = Solidtype_heroui.Labelmodule List_box = Solidtype_heroui.List_box
external prevent_default : Dom.event -> unit = "preventDefault" [@@mel.send]external current_target : Dom.event -> Dom.element = "currentTarget" [@@mel.get]
type form_data
external form_data : Dom.element -> form_data = "FormData" [@@mel.new]external get : form_data -> string -> string Js.null = "get" [@@mel.send]
type animal = { id : string; name : string }
let animals = [| { id = "aardvark"; name = "Aardvark" }; { id = "cat"; name = "Cat" }; { id = "dog"; name = "Dog" }; { id = "kangaroo"; name = "Kangaroo" }; { id = "panda"; name = "Panda" }; { id = "snake"; name = "Snake" }; |]
let list () = <List_box> <Solidtype.For each=(fun () -> animals)> (fun animal _ -> <List_box.Item id=(fun () -> animal.id) text_value=(fun () -> animal.name)> (fun () -> Jsx.string animal.name) <List_box.Item_indicator /> </List_box.Item>) </Solidtype.For> </List_box>
let make () = let message, set_message = S.create_signal None in let sent data name = Option.value (Js.Null.toOption (get data name)) ~default:"null" in <Form class_="flex w-[256px] flex-col gap-4" on_submit=(fun e -> prevent_default e; let data = form_data (current_target e) in S.set set_message (Some ("Key: " ^ sent data "animal" ^ ". Text: " ^ sent data "animal-text" ^ ".")))> <Combo_box is_required=true class_="w-full" form_value=`key name="animal"> <Label>"Animal"</Label> <Combo_box.Input_group> <Input placeholder="Select an animal..." /> <Combo_box.Trigger /> </Combo_box.Input_group> <Combo_box.Popover>(fun () -> Jsx.element (list ()))</Combo_box.Popover> <Description>"Submits the selected key"</Description> <Field_error /> </Combo_box> <Combo_box is_required=true class_="w-full" form_value=`text name="animal-text"> <Label>"Animal (text)"</Label> <Combo_box.Input_group> <Input placeholder="Select an animal..." /> <Combo_box.Trigger /> </Combo_box.Input_group> <Combo_box.Popover>(fun () -> Jsx.element (list ()))</Combo_box.Popover> <Description>"Submits the selected text"</Description> <Field_error /> </Combo_box> <Button class_="w-fit" type_=`submit>"Submit"</Button> (fun () -> match S.get message with | Some m -> Jsx.element <p class_="text-sm text-muted">(fun () -> Jsx.string m)</p> | None -> Jsx.element Jsx.nothing) </Form>Validation Behavior
Section titled “Validation Behavior”HeroUI’s validationBehavior chooses between the browser’s validation and
React Aria’s own. A field here is validated by the application’s state, as
the Form page shows: it is invalid while
is_invalid says so, whenever the application decides.
Custom Validation
Section titled “Custom Validation”HeroUI’s validate is a reading of the value the application keeps:
module S = Solidtype.Solidmodule Jsx = Solidtype.Jsxmodule Button = Solidtype_heroui.Buttonmodule Combo_box = Solidtype_heroui.Combo_boxmodule Field_error = Solidtype_heroui.Field_errormodule Form = Solidtype_heroui.Formmodule Input = Solidtype_heroui.Inputmodule Label = Solidtype_heroui.Labelmodule List_box = Solidtype_heroui.List_box
external prevent_default : Dom.event -> unit = "preventDefault" [@@mel.send]external alert : string -> unit = "alert" [@@mel.scope "window"]
type animal = { id : string; name : string }
let animals = [| { id = "aardvark"; name = "Aardvark" }; { id = "cat"; name = "Cat" }; { id = "dog"; name = "Dog" }; { id = "kangaroo"; name = "Kangaroo" }; { id = "panda"; name = "Panda" }; { id = "snake"; name = "Snake" }; |]
(* The rule HeroUI's example gives its validate, here a reading of the value the application keeps. *)let problem = function | [||] -> Some "Please select an animal" | [| "snake" |] -> Some "Snakes are not allowed" | _ -> None
let make () = let animal, set_animal = S.create_signal [||] in let tried, set_tried = S.create_signal false in let error () = if S.get tried then problem (S.get animal) else None in <Form class_="flex w-[256px] flex-col gap-4" on_submit=(fun e -> prevent_default e; S.set set_tried true; if Option.is_none (problem (S.get animal)) then alert "Form submitted successfully!")> <Combo_box is_required=true is_invalid=(fun () -> Option.is_some (error ())) class_="w-full" name="animal" on_change=(S.set set_animal)> <Label>"Favorite Animal"</Label> <Combo_box.Input_group> <Input placeholder="Search animals..." /> <Combo_box.Trigger /> </Combo_box.Input_group> <Combo_box.Popover> <List_box> <Solidtype.For each=(fun () -> animals)> (fun animal _ -> <List_box.Item id=(fun () -> animal.id) text_value=(fun () -> animal.name)> (fun () -> Jsx.string animal.name) <List_box.Item_indicator /> </List_box.Item>) </Solidtype.For> </List_box> </Combo_box.Popover> <Field_error> (fun () -> Jsx.string (Option.value (error ()) ~default:"")) </Field_error> </Combo_box> <Button class_="w-fit" type_=`submit>"Submit"</Button> </Form>Read Only
Section titled “Read Only”module Combo_box = Solidtype_heroui.Combo_boxmodule Input = Solidtype_heroui.Inputmodule Label = Solidtype_heroui.Labelmodule List_box = Solidtype_heroui.List_box
let make () = <Combo_box is_read_only=true class_="w-[256px]" default_value=(fun () -> [| "cat" |])> <Label>"Favorite Animal"</Label> <Combo_box.Input_group> <Input placeholder="Search animals..." /> <Combo_box.Trigger /> </Combo_box.Input_group> <Combo_box.Popover> <List_box> <List_box.Item id="aardvark" text_value="Aardvark"> "Aardvark" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="cat" text_value="Cat"> "Cat" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="dog" text_value="Dog"> "Dog" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="kangaroo" text_value="Kangaroo"> "Kangaroo" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="panda" text_value="Panda"> "Panda" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="snake" text_value="Snake"> "Snake" <List_box.Item_indicator /> </List_box.Item> </List_box> </Combo_box.Popover> </Combo_box>Multiple Selection
Section titled “Multiple Selection”module Combo_box = Solidtype_heroui.Combo_boxmodule Input = Solidtype_heroui.Inputmodule Label = Solidtype_heroui.Labelmodule List_box = Solidtype_heroui.List_box
let make () = <Combo_box class_="w-[256px]" selection_mode=`multiple> <Label>"Favorite Animals"</Label> <Combo_box.Input_group> <Input placeholder="Search animals..." /> <Combo_box.Trigger /> </Combo_box.Input_group> <Combo_box.Value placeholder="No animals selected" /> <Combo_box.Popover> <List_box selection_mode=`multiple> <List_box.Item id="aardvark" text_value="Aardvark"> "Aardvark" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="cat" text_value="Cat"> "Cat" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="dog" text_value="Dog"> "Dog" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="kangaroo" text_value="Kangaroo"> "Kangaroo" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="panda" text_value="Panda"> "Panda" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="snake" text_value="Snake"> "Snake" <List_box.Item_indicator /> </List_box.Item> </List_box> </Combo_box.Popover> </Combo_box>In Surface
Section titled “In Surface”module S = Solidtype.Solidmodule Button = Solidtype_heroui.Buttonmodule Combo_box = Solidtype_heroui.Combo_boxmodule Field_error = Solidtype_heroui.Field_errormodule Form = Solidtype_heroui.Formmodule Input = Solidtype_heroui.Inputmodule Label = Solidtype_heroui.Labelmodule List_box = Solidtype_heroui.List_boxmodule Surface = Solidtype_heroui.Surface
external prevent_default : Dom.event -> unit = "preventDefault" [@@mel.send]external alert : string -> unit = "alert" [@@mel.scope "window"]
let make () = let animal, set_animal = S.create_signal [||] in (* A required combo box shows its error once a submit has been tried, as the browser's validation React Aria reads does. *) let tried, set_tried = S.create_signal false in let missing () = S.get tried && Array.length (S.get animal) = 0 in <Surface class_="w-[320px] rounded-3xl p-6"> <Form class_="flex w-full flex-col gap-4" on_submit=(fun e -> prevent_default e; S.set set_tried true; if Array.length (S.get animal) > 0 then alert "Form submitted successfully!")> <Combo_box is_required=true is_invalid=(fun () -> missing ()) class_="w-full" name="animal" variant=`secondary on_change=(S.set set_animal)> <Label>"Favorite Animal"</Label> <Combo_box.Input_group> <Input placeholder="Search animals..." /> <Combo_box.Trigger /> </Combo_box.Input_group> <Combo_box.Popover> <List_box> <List_box.Item id="aardvark" text_value="Aardvark"> "Aardvark" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="cat" text_value="Cat"> "Cat" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="dog" text_value="Dog"> "Dog" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="kangaroo" text_value="Kangaroo"> "Kangaroo" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="panda" text_value="Panda"> "Panda" <List_box.Item_indicator /> </List_box.Item> <List_box.Item id="snake" text_value="Snake"> "Snake" <List_box.Item_indicator /> </List_box.Item> </List_box> </Combo_box.Popover> <Field_error>"Please select an item in the list."</Field_error> </Combo_box> <Button type_=`submit>"Submit"</Button> </Form> </Surface>Customization
Section titled “Customization”Tailwind CSS
Section titled “Tailwind CSS”class_ is added after HeroUI’s classes.
module Combo_box = Solidtype_heroui.Combo_boxmodule Input = Solidtype_heroui.Inputmodule Label = Solidtype_heroui.Labelmodule List_box = Solidtype_heroui.List_box
let item_class = "rounded-lg data-[focused=true]:bg-muted/70 data-[selected=true]:font-medium \ data-[selected=true]:text-foreground"
let make () = <Combo_box class_="w-64 gap-1.5"> <Label class_="font-medium text-neutral-800 dark:text-neutral-100"> "Framework" </Label> <Combo_box.Input_group class_="rounded-xl border border-border/80 bg-surface shadow-sm ring-1 \ ring-black/5 transition-[box-shadow,border-color] \ focus-within:ring-2 focus-within:ring-neutral-400/25 \ dark:ring-white/10 dark:focus-within:ring-neutral-500/30"> <Input placeholder="Search..." /> <Combo_box.Trigger class_="text-muted" /> </Combo_box.Input_group> <Combo_box.Popover class_="rounded-xl border border-border/80 bg-surface p-1 shadow-lg \ ring-1 ring-black/5 dark:ring-white/10"> <List_box> <List_box.Item class_=(fun () -> item_class) id="react" text_value="React"> "React" </List_box.Item> <List_box.Item class_=(fun () -> item_class) id="vue" text_value="Vue"> "Vue" </List_box.Item> <List_box.Item class_=(fun () -> item_class) id="svelte" text_value="Svelte"> "Svelte" </List_box.Item> </List_box> </Combo_box.Popover> </Combo_box>Global CSS
Section titled “Global CSS”HeroUI’s classes are global, so a stylesheet restyles every one, as with HeroUI in React:
@layer components { .combo-box { @apply flex flex-col gap-1; }
.combo-box__input-group { @apply relative inline-flex items-center; }
.combo-box__trigger { @apply absolute right-0 text-muted; }
.combo-box__popover { @apply rounded-lg border border-border bg-surface p-2; }}Styling Reference
Section titled “Styling Reference”CSS Classes
Section titled “CSS Classes”.combo-box,.combo-box--full-width.combo-box__input-group,.combo-box__input-group--full-width.combo-box__value,.combo-box__trigger,.combo-box__popover
Interactive States
Section titled “Interactive States”- Open:
[data-open="true"]on the combo box and its trigger - Focus:
[data-focused="true"]on the combo box and its input, and[data-focus-visible="true"]on its input and trigger - Hover and Pressed:
[data-hovered="true"],[data-pressed="true"]on the trigger, pressed while the list is open - Placeholder:
[data-placeholder="true"]on the value - Invalid, Disabled, Read-only, Required:
[data-invalid="true"],[data-disabled="true"],[data-readonly="true"],[data-required="true"]on the combo box - Entering and Exiting:
[data-entering="true"],[data-exiting="true"]on the popover
How it is made
Section titled “How it is made”Kobalte’s combo box filters its options by the input’s text, whatever its
case or accents, keeps the focus in its input while the arrows move through
the list, chooses by Enter or a press, and closes on Escape, as React Aria’s
does. Its list is the List_box inside its popover, which a combo box takes
as its own: the list’s options join the combo box while it is closed too, so
its input can show the option chosen, and are drawn while Kobalte’s filter
keeps them.
Focus opens the list with every option, and so does the trigger; typing filters it. Escape on a closed list puts back the text of the option chosen, and leaving the input does too, unless custom values are allowed. A form is sent the ids chosen by hidden inputs, or the input’s text where it is the value, as React Aria sends them.
While the list is open, the page outside stays as a reader found it, as Kobalte’s combo box leaves it; React Aria’s hides it. Kobalte hides it only while its combo box is modal, which would also hold the page’s pointer and scroll.
API Reference
Section titled “API Reference”Each prop beside the name HeroUI’s React component gives it.
ComboBox
Section titled “ComboBox”| React | solidtype | Type | Default | Description |
|---|---|---|---|---|
selectionMode |
selection_mode |
`single | `multiple |
`single |
One or many, read as it is made |
value, selectedKey |
value |
string array |
The ids chosen, controlled | |
defaultValue, defaultSelectedKey |
default_value |
string array |
The ids chosen at first | |
onChange, onSelectionChange |
on_change |
string array -> unit |
Told the ids chosen as they change | |
inputValue |
input_value |
string |
The input’s text, controlled | |
defaultInputValue |
default_input_value |
string |
The input’s text at first | |
onInputChange |
on_input_change |
string -> unit |
Told the input’s text as it changes | |
onOpenChange |
on_open_change |
bool -> unit |
Told as the list opens or closes | |
disabledKeys |
disabled_keys |
string array |
The disabled options’ ids | |
defaultFilter |
default_filter |
string -> string -> bool |
contains | Keeps an option given its text and the input’s |
menuTrigger |
menu_trigger |
`focus | `input | `manual |
`focus |
What opens the list |
allowsCustomValue |
allows_custom_value |
bool |
false |
Whether a text no option has stays |
allowsEmptyCollection |
allows_empty_collection |
bool |
false |
Whether the list opens with no option |
shouldFocusWrap |
should_focus_wrap |
bool |
false |
Whether the arrows wrap |
isDisabled |
is_disabled |
bool |
false |
Whether it is disabled |
isReadOnly |
is_read_only |
bool |
false |
Whether its value can be read but not changed |
isRequired |
is_required |
bool |
false |
Whether a choice is required |
isInvalid |
is_invalid |
bool |
false |
Whether it is invalid |
name |
name |
string |
Its name in a form | |
formValue |
form_value |
`key | `text |
`key |
Whether a form is sent the ids or the text |
onBlur |
on_blur |
Dom.focusEvent -> unit |
Told as the focus leaves it | |
fullWidth |
full_width |
bool |
false |
Whether it fills its container |
variant |
variant |
`primary | `secondary |
`primary |
Its input’s look |
className |
class_ |
string |
Added after HeroUI’s classes | |
children |
children |
elements | Its label, input group, value, description and popover |
ComboBox.InputGroup, ComboBox.Trigger, ComboBox.Popover
Section titled “ComboBox.InputGroup, ComboBox.Trigger, ComboBox.Popover”| React | solidtype | Type | Description |
|---|---|---|---|
placement |
placement |
`top | `bottom | `left | `right |
The popover’s side, bottom by default |
className |
class_ |
string |
Added after HeroUI’s classes |
children |
children |
elements | What it holds; the trigger’s own icon |
ComboBox.Value
Section titled “ComboBox.Value”| React | solidtype | Type | Description |
|---|---|---|---|
placeholder |
placeholder |
string |
Shown while nothing is chosen |
className |
class_ |
string |
Added after HeroUI’s classes |
children |
children |
string array accessor -> element |
What it draws while something is chosen, given the ids chosen |
Beside React
Section titled “Beside React”Original HeroUI documentation: ComboBox.
The same HeroUI ComboBox twice, under the same props: in solidtype on the left, in React on the right. Below them, every attribute of each input: a marked row is one they disagree on, and a dimmed row is a framework’s own, which no style reads.
| attribute | solidtype | React |
|---|
