Forms
Every control on this page is a native element wearing Barua styling —
text fields, selects, checkboxes and sliders keep their built-in
keyboard, focus and screen-reader behaviour. Validation is baked into
the field shell: one class flips the border, the message and the
announcement together, and :user-invalid works with no
JavaScript at all.
Text Field
The anatomy: a .b-field wraps a .b-label,
the .b-input and an optional .b-help line.
The label is always a real <label for>; the
placeholder only ever shows an example value.
Exactly as it appears on your national ID.
Filled variant
.b-input--filled swaps the border for a soft fill — it
reads better on glass panels and busy backdrops, and grows a border
only on focus.
Sizes
<label for> — tapping the label focuses the
control and screen readers announce the field by name. Link help
text with aria-describedby (as above) so it is read
after the label, and never rely on the placeholder for either job.Text Area
.b-textarea shares the input skin, starts around
5.5rem tall and resizes vertically only — the column never breaks.
Plain text. Drag the corner to make room.
Search Field
A .b-search wrapper turns the input into a filled pill;
the .b-input-group positions icon and shortcut affixes.
Affixes are click-through by default, and the input pads itself
automatically when one is present.
Password Field
An end affix hosts a ghost icon button that swaps the input
type. Buttons inside .b-input-affix--end
regain pointer-events, so only the reveal is clickable —
the rest of the affix stays inert.
At least 12 characters. A phrase beats a puzzle.
Specialized Fields
There is no separate component for numbers or URLs — each is the
same Text Field with a different type, which buys the
right mobile keyboard and free validation. Dates and times are the
exception: their native popups are not ours, so they use the Barua
pickers below instead.
Date & Time Picker
Native date and time inputs open the browser’s own popup — a
surface the system cannot style, and the one thing on screen that
will never look Barua. Product surfaces do not use them. Dates use
the Barua date picker below: the field opens the system
.b-calendar in a dropdown, the same pattern as any
menu. Times use a menu select of slots. Native inputs remain only
as a non-visual fallback where a picker UI truly cannot run.
Barua date picker
A read-only field with the .b-calendar in a
<details> dropdown — fully keyboard reachable,
entirely ours.
28 Aug 2026
Menu time picker
Times are a list, so the time picker is the menu select with slots — shown open, checkmark on the chosen slot.
Select
.b-select strips the platform chrome and draws its own
chevron, so it matches the input family in both themes. It is still
a native <select> — the picker sheet on iOS and
the popup on macOS come along untouched.
Sets your default delivery zone.
Menu select
The native picker opens the OS popup, which breaks the material
world. For product surfaces, a button.b-select opens
the system .b-menu as a listbox instead — checkmarked
options, same field anatomy. A long list scrolls inside the menu
rather than running off the card. Shown open; the state, the hidden
input that carries the value and the arrow keys are yours to wire
(the kit’s Dropdown variant="select" gives the
trigger and the menu).
Combobox & Autocomplete
A .b-combobox pairs a .b-input with an
anchored .b-combobox__list. The previously chosen
option carries aria-selected="true"; the option the
arrow keys are resting on gets .is-active. Both lists
are pinned open here for show — your JS toggles them.
- Mwanza
- Morogoro
- Mtwara
- Mara
- Manyara
- No Tanzanian region matches “Nairobi”.
aria-activedescendant while ↑/↓ move
.is-active, Enter commits and Esc closes. Keep
aria-expanded honest, and give the empty state real
text — a silent dead end is the worst autocomplete bug.Checkbox
A styled native checkbox inside its own label — no wrapper ids, no
for/id bookkeeping. Indeterminate is a JavaScript-only state: set
input.indeterminate = true on a parent whose children
disagree, as the tiny script below does.
.b-checkbox label is the hit area, not just the 20px
box — a full row comfortably clears the 44px touch target on
mobile. Screen readers announce the indeterminate parent as
“mixed”, so the tri-state is conveyed without extra ARIA.Radio Button
Radios travel in groups, and the group needs a name of its own —
wrap it in a .b-fieldset with a legend so
the question is announced before the options.
Toggle Switch
An iOS-grade .b-switch with a sprung thumb. Use it for
settings that take effect immediately; if the choice is submitted
with the rest of a form, that is a checkbox.
Slider
input.b-slider is a native range input with an accent
fill. barua.js finds every one and keeps
--b-slider-fill in sync as the thumb moves, normalising
against whatever min/max you set.
12–24 px. The fill is computed from min/max, not the raw value.
Range Slider
Two native range inputs overlaid in a .b-range: the
track is drawn by the wrapper, the thumbs stay interactive, and the
.b-range__fill bar between them is positioned inline —
here statically, in production from your input handler.
TZS 20,000 – TZS 65,000
Stepper Input
A .b-stepper-input is a number input flanked by − and +
buttons. barua.js wires the pair automatically,
honouring min, max and step —
no data attributes required.
File Upload
The .b-upload dropzone is a label wrapping a hidden
file input, so clicking anywhere opens the picker. Add
.is-dragover from your dragenter handler
to light it up, and list accepted files as
.b-upload-item rows.
Color Picker
A native input[type=color] dressed to sit beside the
other controls, plus a .b-swatches row for presets —
mark the current one with .is-selected.
Form Structure
.b-form stacks .b-form-section cards, each
with a header, its fields, and rows that auto-flow into columns when
there is at least 14rem to spare. .b-form-actions
aligns trailing, like a macOS sheet.
Labels & Help
Required fields get .b-label--required, which appends a
red asterisk; genuinely optional ones say so with a
.b-optional span. When most of a form is required,
mark the optional few instead of starring everything. Keep
.b-help to one calm sentence.
9 digits, as issued by the Tanzania Revenue Authority.
Validation
Add .is-invalid to the field and swap the help line for
a .b-error — border, focus ring and message all turn
danger together. .is-valid confirms with a success
border. Prefer no class at all where you can:
.b-input:user-invalid picks up the danger border only
after the person has actually touched the field, so a pristine form
never opens covered in red.
Enter the full number, e.g. +255 754 123 456.
Looks good — we'll send a confirmation here.
aria-describedby, and the input flags
itself with aria-invalid="true". When errors appear
dynamically, give the .b-error element
role="alert" (or wrap it in a polite live region) so it
is announced without stealing focus. Colour is never the only
signal — the icon and message ride along..b-label stays; the
placeholder only shows an example.