UIDex

Checkbox

مربّع الاختيار

<input type="checkbox">

also calledcheck box، tick box، tri-state checkbox، indeterminate checkbox، select all checkbox، تشيك بوكس، خانة الاختيار، صندوق الاختيار، مربّع التحديد، مربّع صح

A checkbox is a binary field inside a form: selected or not selected, with a value that reaches the server only when the form is submitted. That makes it the control for independent options where more than one may be taken, or none at all. It is not a switch. A switch applies its change at the moment of the press with no save step, while a checkbox stays a draft the user can walk back before submitting. It is not a radio either. Radios force one choice out of a set that rules itself out, whereas every checkbox in a group is fully independent of its neighbours, to the point that required on a checkbox means "this particular box must be ticked" and never "tick one of these". There is a third look called indeterminate, which paints a dash instead of a check, but it is a display state and not a third value: it changes neither checked nor what the form submits.

If you called it…

"the small square you click to turn something on""the I agree to the terms box""the square with a check inside it""the box at the top that picks everything""the one with a dash when only some are picked"

Live specimen

Interact with the demo. Every part is real and numbered.

تنبيهات الحساب

مسطرة المسافة تبدّل المربّع، و Enter يرسل النموذج.

RTLالمربّع عند بداية السطر، والتفريع يزيح بـ padding-inline-start

Anatomy: every part, named

Hover a row to locate it in the demo above.

Build prompt

Build a group of checkboxes inside a form. Make every box a real <input type="checkbox"> with an explicit name and value, and only reach for a <div> if you are ready to write role="checkbox", tabindex="0", aria-checked, and a keydown handler for Space that calls preventDefault so the page does not scroll. Associate every label with for/id, make the whole row a hit area of at least 24 pixels in each direction, and keep links and buttons out of the label. Gather the boxes that answer one question inside a <fieldset> whose first child is a <legend>. Add a select-all box and derive its state from the children: checked when they are all on, indeterminate when only some are, and set the property back to false explicitly when the count fills up or empties. Set indeterminate from JavaScript on the element ref after render, since it is a DOM property and not an HTML attribute, and use aria-checked="mixed" when the box is a custom one. Paint the shape with appearance: none, never hide the field with display: none, and give the box its own :focus-visible ring with outline and outline-offset rather than writing outline: none with nothing behind it. Use logical properties everywhere: gap between box and text, padding-inline-start for the tree indent, border-inline-start for its vertical guide, inset-inline-start for any sticky column, and no margin-right, padding-left, or left anywhere. Never mirror the check glyph when the page flips. On the server, remember that an unchecked box sends nothing at all, so a missing key means false.

How it behaves right-to-left

This section is ours alone.

Where the box sits

mirrors

The box sits at the logical start of the line, which in Arabic is the right, and the correct DOM order (box first, then text) gives you that in both directions without a single extra rule. Space the two with display: flex and gap, because a margin-right on the box opens the gap on the wrong side once the page flips. Do not add flex-direction: row-reverse under [dir="rtl"] thinking you are fixing direction: the inline axis flipped once already, and the second flip drags the box back to the left while leaving the visual order at odds with Tab order.

The check and the dash

never mirrors

A checkmark does not mirror with page direction: the Bidi_Mirrored property of the check character is No, and the browser painting of a native box does not change in RTL either. A blanket [dir="rtl"] svg { transform: scaleX(-1) } breaks the glyph you drew by hand and leaves it back to front. The indeterminate dash is symmetric about its vertical axis, so the same bad rule leaves no mark on it at all, which is how the bug clears review and turns up later on the checked state alone.

Indenting a nested tree

mirrors

Indent the children of a checkbox tree with padding-inline-start rather than padding-left, so the steps come in from the right in Arabic and from the left in English. A <ul> gets this right on its own, since the browser stylesheet gives it padding-inline-start: 40px; the usual mistake is overriding that with padding-left: 1.5rem, which sends the whole tree back to the wrong side. The vertical guide line joining the children is a border-inline-start, never a border-left.

The focus ring

never mirrors

A ring drawn with outline is direction neutral: outline surrounds all four sides and outline-offset pushes it out from each of them by the same amount, so nothing here needs flipping. Hand-rolled rings are what break: box-shadow: -3px 0 0 var(--ring) has no logical form at all, and a ::before pinned with left: -4px stays on the left after the flip and lands over the text instead of around the box, where inset-inline-start: -4px would have followed the direction. The clipped side moves with it, so a row carrying overflow: hidden, or less padding than the ring is thick, shaves the ring off at the logical start where the box now sits; leave a padding-inline-start wide enough to hold it.

The select column in a table

mirrors

The checkbox column in a table is the first column, which puts it at the far right in Arabic, and the column order mirrors on its own because the table follows direction. Pinning it with position: sticky and left: 0 leaves it glued to the left edge after the flip, where it floats over the other columns instead of holding the first one, so the value you want is inset-inline-start: 0. The divider marking that column off is a border-inline-end, not a border-right.

In code

Each row is one framework's word for the same thing. Take the row your project speaks.

HTML<input type="checkbox" name="…" value="…">An unchecked box sends no key at all, and with no value every checked one arrives as "on".
HTMLinput.indeterminate = trueA property with no HTML attribute behind it, reachable only from JavaScript after render.
ARIArole="checkbox" + aria-checked="mixed"The only way to announce the third state on a custom box; a native one announces it by itself.
CSSappearance: noneDrops the native painting only, so the border, the fill, and the glyph become yours to draw.
HTML<fieldset> + <legend>Names the whole group, and only the first legend child inside the <fieldset> supplies it.
Radix UI<Checkbox.Root checked="indeterminate">The third state is a string inside the same checked prop, and Checkbox.Indicator paints for it and for true.
Material UI<Checkbox indeterminate />A separate prop that paints the dash and outranks checked visually without changing its value.

See also

Updated · 2026-08-19