Skip to content
Barua UI v0.2
Components

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

Accessibility. Every input pairs with a real <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.

Travel date
28 Aug 2026
August 2026
MTWTFSS

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.

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”.
Accessibility. DOM focus never leaves the input: the visually focused option is referenced by 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.

Accessibility. The entire .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.

Payment method

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.

Price range

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.

zanzibar-ferry-eticket.pdf
1.2 MB
amina-profile-photo.jpg
640 KB

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.

Profile settings

How you appear across Barua apps.

Used for sign-in and receipts.

Notifications

Choose what lands in your inbox.

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.

Accessibility. The error is real text tied to the input with 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.
Validate a field when it loses focus, then re-check on every keystroke once it has erred — the message appears beside the field it belongs to, and clears the moment the input is fixed.
Don't use the placeholder as the label. It vanishes on the first keystroke, fails contrast, and leaves invalid fields describing nothing. The .b-label stays; the placeholder only shows an example.