Checkbox

v0.0.10

v0.0.10StableMobStatelessApache-2.0
Installation mix mishka.ui.gen.mob checkbox
Source View Source

The Mob Checkbox is a labeled box with checked, unchecked and indeterminate states for iOS and Android apps built with Mob. It is part of Mishka Chelekom and is the native port of the headless Checkbox for Phoenix LiveView.

Overview of the Mob Checkbox component for iOS and Android

Mob has no checkbox widget, because its Toggle is a switch. MishkaCheckbox builds the indicator itself: a small rounded Box that holds a drawn mark, beside the label, in one tappable row.

A checkbox has three states. An empty box is unchecked, a tick is checked and a dash is indeterminate. While indeterminate is set, it wins over checked, the way aria-checked="mixed" overrides the checked flag on the web. The states differ by shape as well as color, so people who cannot tell the colors apart can still read them.

The screen owns the state. A tap sends {:tap, tag} to handle_info/2, and the screen decides the next checked and indeterminate values. toggle/1 returns the next {checked?, indeterminate?} for a tap. A mixed checkbox resolves to checked, so tapping a select all parent selects everything.

The mark is drawn with canvas lines instead of a text glyph. A glyph sits on its text baseline and rides high in a small box. Drawn lines have no text metrics, so the mark stays centered at any size on both platforms.

Generate the checkbox component

Run this from your Mob app's root to add the component to lib/<app>/components/:

bash
mix mishka.ui.gen.mob checkbox

The generator registers the component's tag, so you can use it in ~MOB markup. It also copies the shared Event module. The examples on this page use the mishka_ prefix of the showcase app (--module-prefix mishka_), so the tag is <MishkaCheckbox>. Without a prefix the tag is <Checkbox>. To generate the prefixed version, run:

bash
mix mishka.ui.gen.mob checkbox --module-prefix mishka_

Checked and unchecked checkbox

A checkbox is one tappable row made of the box and its label. Tapping anywhere on the row sends {:tap, tag} to the screen with the on_toggle tag, and the screen flips checked.

Checked and unchecked checkbox

Card titled Checked and unchecked, with two checkbox rows: Remember me with a tick in a dark filled box and Unchecked with an empty box
heex
<MishkaCheckbox label="Remember me" checked={@remember?} on_toggle={:remember} />

Select all checkbox with an indeterminate state

A parent checkbox goes indeterminate when only some of its children are checked. Set indeterminate on the parent and it draws a dash. Call toggle/1 to get the next state. A tap on a mixed parent resolves to checked, so it selects everything.

Select all checkbox with an indeterminate state

Card titled Select all (indeterminate), with an All languages parent checkbox showing a dash above a divider and three rows: BEAM checked, OTP and Phoenix unchecked
A parent checkbox All languages and its three children BEAM, OTP and Phoenix, all checked.

Left: the example as it loads. Right: after a tap. The parent goes mixed when only some children are ticked.

elixir
<MishkaCheckbox label="All" checked={all?} indeterminate={some?} on_toggle={:all} />

# tapping a mixed parent selects everything
{checked?, _} = MishkaCheckbox.toggle(%{indeterminate: true})

Disabled checkbox

disabled wires no handler and mutes the row, in any state. A disabled checkbox can stay checked or unchecked, and a tap on it does nothing.

Disabled checkbox

Card titled Disabled, with two muted checkbox rows: Locked on with a gray tick and Locked off with an empty box, in the MishkaMob showcase app on Android
heex
<MishkaCheckbox label="Locked" checked={true} disabled={true} />

Checkbox color and size

color fills the box when it is checked or mixed, and size sets the edge length of the box. The mark scales with the box.

Checkbox color and size

Card titled Colour and size, with a larger checked box filled violet labeled Violet, larger and a smaller checked dark box labeled Small, in the MishkaMob showcase app on Android
heex
<MishkaCheckbox label="Violet" checked={true} color={0xFF7C3AED} size={26} />

Props

PropTypeDefaultDescription
labelstringnilText beside the box.
checkedbooleanfalseChecked state. The screen owns it.
indeterminatebooleanfalseMixed state. It overrides checked and draws a dash.
disabledbooleanfalseWires no handler and mutes the row.
on_toggleevent tagNoneSent as {:tap, tag}.
colorcolor / ARGB:primaryFill when checked or mixed.
sizenumber22Edge length of the indicator.
idstringNoneNative test tag. The component adds the state to it: id-checked, id-mixed or id-empty.
toggle/1helperNoneReturns the next {checked?, indeterminate?} that a tap should produce.

Events

Your screen receives {:tap, tag} in handle_info/2. The message has no value, so the screen computes the next state. Use toggle/1 from the generated MishkaCheckbox module to get it:

elixir
def handle_info({:tap, :remember}, socket) do
  {checked?, _indeterminate?} = MishkaCheckbox.toggle(%{checked: socket.assigns.remember?})
  {:noreply, Mob.Socket.assign(socket, :remember?, checked?)}
end

toggle/1 returns {true, false} for an unchecked box, {false, false} for a checked box and {true, false} for an indeterminate box.

Differences from the web component

  • name, value and required are not ported. They are HTML form plumbing.
  • The web id and the *_class attributes are not ported. The Mob id prop sets a native test tag instead.
  • parent is not ported. The select all behavior is a reducer in the screen over its children, and toggle/1 is the part worth sharing. The Checkbox group ships the full set of reducers.
  • readonly and disabled become one prop. Both omit the handler.