Five pickers, in Erabi.Components, with the same attributes.

select

One value from a list, as a <select>: a box that opens the list. With the field focused, the arrows open it and move, Enter and Space choose, and letters move to the option that starts with them (typeahead). A list of 10 options or more has a box to filter it (filter={false} takes it away; filter adds it to a shorter list).

<.select field={@form[:priority]} options={@priorities} />

multi_select

Several values, shown as chips. The list stays open as people check and uncheck; Backspace on the field takes the last chip back, and a chip's × takes it. min and max count the values: past max, the other options cannot be chosen, and say why.

<.multi_select field={@form[:labels]} options={@labels} max={3} />

combobox

A box to type in that suggests options as people type, and takes one. The chosen option's label is in the box. With create, what people type is kept as it is when they leave the box, under the rules (see Rules): a "city, or any place" field.

<.combobox field={@form[:city]} options={@cities} />
<.combobox field={@form[:place]} options={@cities} create />

tags

Chips that people type. Suggestions come as they type; Enter takes the active one, a comma takes what they typed; with create, "Create “…”" is at the end of the list, refused with the reason when the rules say no (a pattern, a length).

<.tags field={@form[:tags]} options={@tags} create max_length={24} />

person_picker

People and agents: avatars (an image, or initials on the person's color), agents with a rounded avatar and a badge, me first and marked. One person, or several with multiple.

<.person_picker field={@form[:owner_id]} people={@members} me={@current_user} />
<.person_picker field={@form[:reviewer_ids]} people={@members} me={@current_user} multiple />

People are maps or structs with :id and :name, and :avatar, :color, :kind (:human or :agent) when they have them: your users, and Issho's actors, as they are. See Options.

color_picker

The color picker is an entry of its own, which the app imports with the hook (import "erabi/color"): apps without one do not load it.

A color: swatches of a palette, and, with custom, any color. The palette is Erabi's nine option colors by default, posted by name ("blue") and drawn as the chips they make, so that a label's color reads the same where it is chosen and where it shows; or your colors, as options.

<.color_picker field={@form[:color]} />
<.color_picker field={@form[:brand]} palette={[{"Ocean", "#3f6f8f"}, {"Sand", "#d8c3a5"}]} custom contrast="#ffffff" />

With custom, the popup has an area of lightness and chroma for a hue, and a hue slider, in OKLCH, which follows how people see colors: each row of the area runs out to the most chroma a screen shows, so every point is a color it can show. Then a hex box, the browser's eyedropper where it has one (Chromium), and the person's recent colors (kept in the browser). A custom color posts as #rrggbb; custom is the rules' create, with a pattern of six hex digits, which the changeset takes too:

|> Erabi.Changeset.validate_choice(:color, [], options: Erabi.Option.colors())
|> Erabi.Changeset.validate_choice(:brand, create: true, pattern: "#[0-9a-fA-F]{6}")

contrast is the background the color is for: the custom colors show their contrast ratio with it, and their WCAG grade.

The swatches are a listbox: arrows move between them in rows, Home and End, Enter or Space takes one. The area and the hue are sliders: arrows move by 1 (Shift, by 5 or 10), Enter takes the color and closes.

emoji_picker

An emoji, as Notion's page icons and Slack's reactions take one: every emoji by category, a search in the page's language, skin tones, the person's recent emoji, and a preview of the one under the pointer or the keyboard. It posts the emoji itself ("🎉", in the chosen tone).

<.emoji_picker field={@form[:icon]} />

With options that have an :icon, drawn by the :icon slot, it has a second tab, of your icons, which post their values:

<.emoji_picker field={@form[:icon]} options={@icons}>
  <:icon :let={name}><.icon name={"hero-" <> name} /></:icon>
</.emoji_picker>

The search finds an emoji by the start of a word of its name, its tags or its :shortcode: ("thu", "+1" and ":thumbsup:" find 👍), in English, Portuguese, Spanish, French, German, Italian, Dutch or Japanese, and stays under a few milliseconds a key. Emoji newer than the device draws are left out. tones={false} takes the skin tones away.

The emoji are a file of the page's language (about 60 KB compressed), which the picker fetches as it first opens, or as the pointer comes near it, and which the browser then keeps. mix erabi.install serves them from your endpoint; see Erabi.Emoji. Check the value in the changeset with Erabi.Emoji.options/1:

|> Erabi.Changeset.validate_choice(:icon, [], options: Erabi.Emoji.options(@icons))

On the server, Erabi.Emoji.find/2 searches as the picker does, and Erabi.Emoji.name/2 names an emoji: an agent's tool can choose an emoji by what it means.

The keyboard: type to search; the arrows move in the grid, by rows across categories; Enter takes the emoji; Escape clears the search, then closes. The emoji and icon pickers are an entry of their own, which the app imports with the hook (import "erabi/emoji").

icon_picker

One of your icons: the options that have an :icon, in a grid, with a search by their labels and keywords. It posts the option's value.

<.icon_picker field={@form[:icon]} options={@icons}>
  <:icon :let={name}><.icon name={"hero-" <> name} /></:icon>
</.icon_picker>

Attributes of every picker

fieldthe form field, @form[:status]; or name and value
idthe picker's element; the field's id with _picker unless given
optionsthe options (see Options)
selectedoptions of chosen values that are not in options (a search's), for their labels
search, asyncoptions from the server as people type (see Search)
rules, required, min, max, createthe rules (see Rules)
filtera box to filter the list; from 10 options unless given
clearablea clear button on a picker of one value; unless required
placeholderthe words of an empty picker
label_id, labelthe accessible name; a <label for> of the field works too
disabled, readonlyas on any field
locale, messagesthe words (see Erabi.Messages)
on_changean event the picker pushes on each change, outside a form
sheetwhere the popup is a sheet from the bottom of the screen: on phones (:auto), :always, :never; config :erabi, sheet: for every picker
:icon slotdraws an option's :icon by its name

On phones

On a phone (a touch screen narrower than 640px, or shorter than 480px, as a phone on its side), every picker's popup is a sheet from the bottom of the screen, as the phone's own pickers are: over a dimmed page that does not scroll, with the picker's name and a Done button, rows of 2.75rem, and as tall as the screen allows above the on-screen keyboard. A combobox's or tags' box to type in moves into the sheet while it is open, so that it stays above the keyboard. A tap on the dimmed page (which then does nothing on the page), Done, or a swipe down from the sheet's top closes it. Text boxes in a sheet are 16px at least, so that the phone does not zoom in on them.

sheet={:never} keeps a picker a dropdown everywhere; sheet={:always} makes it a sheet on every screen.

Outside a form

A picker that is not in a form (a view switcher, a filter of a page) pushes on_change with %{"id" => id, "value" => value}:

<.select name="view" value={@view} options={["Board", "List"]} label="View" on_change="view" />

From the server

Erabi.Live sets a picker's value (set/3), opens and closes its list, and moves the focus to it, for a value the server changed while the picker is on the page.