Design Guidelines
At OSBR we design the experience — how a product behaves in a person's hands — while its look and feel are shaped elsewhere. This page holds the standards for that behaviour: how the interface should respond, explain itself, and stay out of the user's way. It is the counterpart to the code Style Guide, which governs how we write; this one governs how the thing we build feels to use. Where the Style Guide asks whether the code is sound, these guidelines ask whether the experience is reachable, self-evident, complete in every state, and free of traps. Getting this right is Be Nice in the interface — thinking wholeheartedly about the person on the other side of the screen.
In depth: Accessibility · Self-Explanatory UI · Modeless Design · Interaction Design
Accessibility is the floor
Accessibility is a starting constraint, not a finishing touch. We design it in from the first interaction, the same way we ask "does it work" from the first line of code. A control that cannot be reached by keyboard, or an image with no text alternative, is an unfinished feature — exactly as a broken button is — not a defect to be swept up in a later "accessibility pass" that never quite arrives. The reasoning is one idea: an interface that states its meaning and its operations explicitly is reachable by whoever shows up, because assistive technology consumes a product through its declared structure, not its pixels. Designing for the edges makes the centre better.
A published baseline keeps "accessible" a verdict, not a feeling. We hold every product to WCAG 2.2 AA as the minimum — the target is a product people actually find easy, and the floor is what we never ship below. We build from semantic HTML first, because native elements carry role, state, focus, and keyboard behaviour for free, and we reach for WAI-ARIA only to fill a gap no native element can express — never to re-create a native control on a bare <div>. And we verify with more than a linter: automated checks catch perhaps a third of issues and cannot tell us whether a flow makes sense announced aloud or driven by Tab alone.
- We MUST meet WCAG 2.2 AA as the floor on every product, and treat a missing label or an unreachable control as a defect, not a nice-to-have.
- We MUST verify each UI-bearing feature keyboard-only and with a screen reader before it ships — every control reachable and operable, a visible focus indicator, no keyboard traps, and an announced name, role, and reading order that match the visual order.
- We MUST build from semantic HTML first, using ARIA only to supplement what no native element provides.
- We SHOULD review designs for keyboard order, focus, and non-visual meaning before implementation begins, so accessibility is scoped into the work rather than retrofitted onto it.
The interface explains itself
A finished screen is operable without documentation. If a person needs a manual, a walkthrough video, or a guided tour to complete an ordinary task, the screen is doing less than its job and pushing the shortfall onto the user. Each screen carries the whole conversation: whatever it fails to say, the user must guess, ask, or look up — and every guess is a chance to get it wrong. So the design's job is to say it, on the control, in the user's own words, at the moment and place it is needed. A first-time user, given the screen and nothing else, should be able to tell what it is, what they can do, what is happening, and what to do next.
We speak the user's language, and let controls signal their use. Labels, buttons, and messages use the words the user uses, not our internal jargon — if the domain term is "consignment", the button is not "Submit Record". Interactive elements look interactive; a control that relies on the user hovering or guessing that it is clickable has hidden itself. We write plainly — short sentences, common words, active voice — so a screen needs no glossary. And a reached-for onboarding tour is a signal, not a solution: it usually means a label, a signifier, or an empty state was skipped, and the honest fix is the screen underneath, not an overlay narrating it. A one-time hint that teaches an optional power-feature is fine; a tour required to do the ordinary task is a defect, because users skip tours, forget them, and arrive by deep link having never seen them.
- We MUST write every label, action, and message in the user's vocabulary, reusing the product's established terminology.
- We MUST give every control a signifier that it is interactive and what it will do, and arrange controls so their layout mirrors what they affect.
- We SHOULD treat a needed guided tour or coach-mark as a design finding — investigate which of the practices above the screen skipped, and fix that instead of narrating over it.
Every state is designed
A screen designed only for "full of data and working" is a screen designed for the state users spend the least time in. Every screen that fetches, shows, or accepts data lives in four states — loading, empty, error, success — and the three beyond the happy path are exactly where an undesigned screen abandons the user. Loading must show that something is happening, not a frozen screen or an ambiguous spinner that could equally mean "working" or "hung". An empty screen is a first impression, not a fault: it explains what belongs there and offers the single action to create the first item, never a blank void that reads as broken. Success must confirm the outcome, so the user is not left re-checking whether their action took. A screen shipped with only its success-with-data state designed is not finished — the other three get filled by browser defaults, blank space, and raw stack traces, which is to say by no design at all.
An error message is the screen speaking at the user's worst moment, so it says both halves: what happened and what to do. "Something went wrong" states neither; "Card declined — check the number and expiry, or try another card" states both. The message sits next to its cause — a field error at the field, a form-level error where the eye already is — because distance makes the user hunt for what to fix. Better still, we prevent the error first: constrain inputs, disable impossible actions, confirm destructive ones. We never blame the user and never hide the failure; a silent failure is worse than a blunt one.
These four states are defined once, before the second screen, and reused everywhere. A user should learn the product a single time — one loading treatment, one way an error looks and where, one shape for an empty list, one confirmation for a class of action. The first screen sets no precedent alone; the second is where consistency is either won or forked, because every later screen copies whichever way the divergence went. So we settle the states as shared components early, and a second list that invents its own near-identical empty state is a divergence even when it looks similar — "similar" is exactly what the user notices and mistrusts. For structured content with a known layout, a skeleton is the default: it preserves the page's shape and reads as "arriving", where a bare spinner reads as "stuck".
- We MUST design all four states — loading, empty, error, and success — for every screen that fetches, shows, or accepts data, not the happy path alone.
- We MUST make every error message state what happened and what to do, in plain language, adjacent to its cause, and prevent the error at the source where we can.
- We MUST define the four states as shared, reusable components before the second screen consumes them, and reuse them rather than re-create near-duplicates.
- We SHOULD use one signal per meaning across the product — one treatment for a class of success, one placement rule per kind of error — and default to a skeleton over a spinner for content with a known layout.
Interaction is modeless and reachable
Modelessness is the default; a mode is the exception that must justify itself. A mode is any state where the same action produces a different result depending on where the interface currently is — most visibly a modal dialog that seizes the screen and refuses every action but its own. Modes are a principal cause of user error, because the person acts on their intent while the system acts on its hidden state, and the two diverge. So editing, creating, filtering, and previewing happen inline, in a panel, or on their own page — somewhere the user can leave and return to. A modal is warranted only for an irreversible confirmation (a delete or permanent send, where a reversible action would be better served by undo than by a stop-and-confirm), a single indivisible submission that would be corrupted if left half-done in the background, or a physically-exclusive interaction that genuinely needs the full surface. If the interaction is none of these, it is not a modal — "it was easier to drop in a dialog" is the failure mode this guideline exists to catch — and when a modal is warranted, we record which case it falls under, so the exception stays auditable.
The place the user is in belongs in the URL, and every mode has more than one way out. State held only in transient memory is a place with no address: the user cannot bookmark it, share it, reload into it, or step back out with the browser's own Back button — which is a modeless exit we get for free the moment the current place lives in the URL. When a mode must exist, leaving it is trivial and never punishes the user: closing preserves their in-progress input rather than eating the three fields they typed before pressing Escape by reflex. Cancelling is always safe, so it is effortless and available every way; only committing something irreversible earns a deliberate, guarded gesture. And a modal serves two audiences at once — we trap keyboard and screen-reader focus inside it so no one tabs out into inert background controls, while keeping that background visible and readable, because a person answering "delete this invoice?" should still be able to see which invoice.
A learned key does the same thing everywhere. Keyboard behaviour is where inconsistency hides most invisibly and hurts most, because a keyboard or screen-reader user navigates entirely by learned conventions. We adopt the WAI-ARIA Authoring Practices keyboard contract for each pattern rather than inventing our own bindings, and hold every instance of a pattern to the same keys — every menu to the same arrow-key movement, every dismissible surface to Escape. Focus follows reading order and is always visible. When a screen genuinely must depart from the standard, we record the deviation in our design system's interaction notes — an unrecorded deviation is indistinguishable from a mistake and gets copied as if it were the convention, and a deviation that recurs three times is a signal to update the standard, not to keep forking.
- We MUST treat modelessness as the default, use a modal only for an irreversible confirmation, a single indivisible submission, or a physically-exclusive interaction, and record the justification when we do.
- We MUST hold view, selection, and "which item is open" state in the URL so a place is addressable, shareable, and survives reload, and so Back closes what Back should close.
- We MUST give every modal at least two exits — Escape and a background click, alongside any Close control — preserve in-progress input on close, and trap focus inside it while keeping the background readable and the dialog correctly announced (
role,aria-modal, and a label). - We MUST implement the keys the WAI-ARIA Authoring Practices specify for each pattern, use them identically across every instance, and keep focus visible and in reading order.
- We SHOULD reuse our design system's existing component, state, and tokens over a one-off variant, and record every deliberate deviation — with what differs and why — in the shared interaction notes.