All the patterns on this page are made with existing compositions and classes.
Label + Field Unit
Most systems have form-related classes for managing layout in a form. Brio takes a slightly different approach by relying more on existing compositions that exist in the library. This reduces the amount of CSS and HTML needed to build forms, and you can reuse a familiar set of composition patterns.
For forms, I tend to use a small wrapper with `.stack.is-tight. The label
sits directly before the control, with the control directly underneath.
.stack helps keep the vertical layout between them consistent.
<div class="stack is-tight" style="max-inline-size: 24rem">
<label for="unit-name">Name</label>
<input id="unit-name" type="text" class="input" />
</div>
You can do something similar with the label to the left or right of an input control by
using the .cluster.is-snug composition instead.
<div class="stack">
<div class="cluster is-snug" style="max-inline-size: 24rem">
<label for="unit-name-left">Name</label>
<input id="unit-name-left" type="text" class="input" style="flex-grow: 1" />
</div>
</div>
Required fields
A small * is added to the label when
the native required attribute is present. When the label directly precedes
a required control inside the stack, Brio draws the asterisk itself and keeps it out of
the field's accessible name, so the label you write is the label a screen reader
describes.
A field without required reads as optional. If almost every field in a form
is required, turn it around: mark only the exceptions and write
(optional) in their labels.
<div class="stack is-snug" style="max-inline-size: 24rem">
<div class="stack is-tight">
<label for="unit-email">Email</label>
<input id="unit-email" type="email" class="input" required />
</div>
<div class="stack is-tight">
<label for="unit-company">Company (optional)</label>
<input id="unit-company" type="text" class="input" />
</div>
</div>
Help text
Helper text often goes udner the control, usually muted and one step smaller, and wired
with
aria-describedby pointing at its id. It can be used to
describe the field's purpose or expected format. I like to add helper text, because
placeholder text disappears the moment someone starts typing, and helper text does not.
Lowercase letters, numbers, and dashes. This is how you appear in mentions.
<div>
<div class="flow flow-space-2xs" style="max-inline-size: 24rem">
<label for="unit-handle">Handle</label>
<input
id="unit-handle"
type="text"
class="input"
required
aria-describedby="unit-handle-help"
/>
<p id="unit-handle-help" class="text-muted text--1">
Lowercase letters, numbers, and dashes. This is how you appear in mentions.
</p>
</div>
</div>
Error messages
An error message is often reported by the server, or sometimes by the frontend,
depending on the project. It's used to describe what went wrong or what validation step
failed. It's similar to the help text, but usually uses text-danger, and
the control it belongs to usually has the aria-invalid attribute set to
"true", which makes the border and the text change together. If the field also has
standing help text, list both ids, space separated; a screen reader then announces the
help and the verdict in one pass.
Twelve characters, from your invitation email.
That code has expired.
<div>
<div class="stack is-snug" style="max-inline-size: 24rem">
<div class="stack is-tight">
<label for="unit-code">Invite code</label>
<input
id="unit-code"
type="text"
class="input"
required
aria-invalid="true"
aria-describedby="unit-code-help unit-code-error"
/>
</div>
<p id="unit-code-help" class="text-muted text--1">
Twelve characters, from your invitation email.
</p>
<p id="unit-code-error" class="text-danger text--1">That code has expired.</p>
</div>
</div>