Utilities
tier 2 — classes
Single-purpose classes, in @layer dr.utilities. Not a utility framework — the point
of tier 2 is semantic classes, and a page assembled out of fifteen utilities has moved its design
decisions into the markup where nobody reviews them.
(0,1,0)
and a component rule like .table th, .table td is (0,1,1), so a utility
loses on specificity alone. dr.utilities sits after dr.paint, so
.text-end on a table cell wins regardless of it.
.dr-row and
.dr-col already say it with meaning, and no breakpoint prefixes, because the library
has no breakpoint vocabulary and should not gain one.
Rows and columns
.dr-row, .dr-row-wrap and .dr-col are the arrangements
that would otherwise be an inline style="display:flex; gap:8px" — which is most
of them. .dr-push pushes everything after it to the far end.
These carry the dr- prefix because they are library-owned utilities in a shared
namespace, where .row and .col are names an app is very likely to
have already.
<div class="dr-row dr-w-full" style="border:1px solid var(--border); padding:10px">
<strong class="text-mono">ORD-4182</strong>
<span class="badge badge-warn">Awaiting stock</span>
<span class="dr-push"></span>
<button class="btn btn-sm" type="button">Open</button>
<button class="btn btn-sm btn-primary" type="button">Dispatch</button>
</div>
Gaps
.dr-row and .dr-col carry one gap. These four override it:
.dr-gap-0 removes it, and 1, 2 and 3 are
8px, 12px and 20px from the spacing scale.
<div class="dr-col dr-gap-3">
<div class="dr-row dr-gap-0">
<span class="badge">dr-gap-0</span><span class="badge">no gap</span><span class="badge">at all</span>
</div>
<div class="dr-row dr-gap-1">
<span class="badge">dr-gap-1</span><span class="badge">8px</span><span class="badge">the default</span>
</div>
<div class="dr-row dr-gap-2">
<span class="badge">dr-gap-2</span><span class="badge">12px</span>
</div>
<div class="dr-row dr-gap-3">
<span class="badge">dr-gap-3</span><span class="badge">20px</span>
</div>
</div>
Alignment
.dr-row centres on the cross axis. .dr-start is what a row of fields
with labels wants; .dr-baseline is what a row of text at different sizes wants.
.dr-between pushes the ends apart, and .dr-wrap allows a second
line.
<div class="dr-col dr-gap-2">
<div class="dr-row dr-start" style="border:1px dashed var(--border); padding:8px">
<span class="badge">dr-start</span>
<span class="dr-col dr-gap-0"><span>Two lines,</span><span>top-aligned.</span></span>
</div>
<div class="dr-row dr-baseline" style="border:1px dashed var(--border); padding:8px">
<span class="badge">dr-baseline</span>
<span class="text-lg">Large</span>
<span class="text-sm">and small, on one baseline.</span>
</div>
<div class="dr-row dr-end" style="border:1px dashed var(--border); padding:8px">
<span class="badge">dr-end</span>
<span class="dr-col dr-gap-0"><span>Two lines,</span><span>bottom-aligned.</span></span>
</div>
<div class="dr-row dr-between" style="border:1px dashed var(--border); padding:8px">
<span class="badge">dr-between</span>
<span class="text-muted">pushed apart</span>
</div>
<div class="dr-row dr-wrap" style="max-width:260px; border:1px dashed var(--border); padding:8px">
<span class="badge">dr-wrap</span>
<span class="badge">onto</span>
<span class="badge">a second</span>
<span class="badge">line</span>
<span class="badge">when needed</span>
</div>
</div>
Sizing and spacing
.dr-fill is the flex child that takes the remaining room, and it includes
min-width: 0 — without which the child refuses to shrink below its content and
every ellipsis inside it stops working. .dr-shrink-0 is the opposite, for the
avatar and the badge that must not compress.
.dr-mt-* and .dr-mb-* are block margins only, in the same four
steps. There is no inline pair: horizontal placement belongs to the layout, not to a utility
on the child. .dr-scroll-x creates a horizontal scroller —
.dr-scroll styles one, which is a different job.
dr-mt-0 kills a component's own top margin; dr-mb-1 adds 8px.
dr-mt-1 and dr-mb-2 — 8px above, 12px below.
dr-mt-2 and dr-mb-3 — 12px above, 20px below.
dr-mt-3 adds 20px; dr-mb-0 removes the last bottom margin.
| Order | Customer | Region | Warehouse | Total |
|---|---|---|---|---|
| ORD-4182 | Priya Nair | EU-West | Rotterdam | 1,240.00 |
<div class="dr-col dr-gap-2" style="max-width:420px">
<div class="dr-row" style="border:1px dashed var(--border); padding:8px">
<span class="avatar avatar-sm dr-shrink-0">JW</span>
<span class="dr-fill text-truncate">
A very long single line that has to be cut rather than push the badge out of the row
</span>
<span class="badge dr-shrink-0">dr-fill</span>
</div>
<div style="border:1px dashed var(--border); padding:8px">
<button class="btn dr-w-full">dr-w-full</button>
</div>
<div class="dr-row dr-center" style="border:1px dashed var(--border); padding:8px">
<span class="badge">dr-center</span>
<span class="dr-col dr-gap-0"><span>Two lines,</span><span>centred on the cross axis.</span></span>
</div>
<div class="dr-col dr-gap-0" style="border:1px dashed var(--border); padding:8px">
<p class="dr-mt-0 dr-mb-1">dr-mt-0 kills a component's own top margin; dr-mb-1 adds 8px.</p>
<p class="dr-mt-1 dr-mb-2">dr-mt-1 and dr-mb-2 — 8px above, 12px below.</p>
<p class="dr-mt-2 dr-mb-3">dr-mt-2 and dr-mb-3 — 12px above, 20px below.</p>
<p class="dr-mt-3 dr-mb-0">dr-mt-3 adds 20px; dr-mb-0 removes the last bottom margin.</p>
</div>
<div class="dr-scroll-x dr-scroll" tabindex="0" role="group" aria-label="Order detail table"
style="border:1px dashed var(--border)">
<table class="table" style="min-width:640px">
<thead><tr><th>Order</th><th>Customer</th><th>Region</th><th>Warehouse</th><th class="col-num">Total</th></tr></thead>
<tbody><tr><td>ORD-4182</td><td>Priya Nair</td><td>EU-West</td><td>Rotterdam</td><td class="col-num">1,240.00</td></tr></tbody>
</table>
</div>
</div>
Text
The alignment utilities are logical (start / end), so they mirror
with the document direction like everything else in the library.
.text-truncate is one line with an ellipsis and includes
min-width: 0 for the same reason as .dr-fill.
.text-clamp-2 and -3 cut after that many lines.
.text-break breaks inside a word when there is nowhere else — an id, a URL, a
path in a narrow column.
.text-sm and .text-lg are one type step either side of the body
size, for a line that has to be quieter or louder without becoming a heading.
| Default alignment | 1,204 |
| Muted | 86 |
| whk_live_8f2c1d | 0 |
.text-clamp-3 cuts after three lines with an ellipsis, however long the text is. This paragraph is deliberately longer than two lines so that the cut is visible, and it goes on for a while after that to make the point.
.text-break/pipelines/eu-west/reconciliation-2026-08-03T12-04-31Z.ndjson
<div class="dr-col" style="max-width:420px">
<table class="table">
<tr><td>Default alignment</td><td class="text-end text-nums">1,204</td></tr>
<tr><td class="text-muted">Muted</td><td class="text-end text-nums">86</td></tr>
<tr><td class="text-mono">whk_live_8f2c1d</td><td class="text-end text-nums">0</td></tr>
</table>
<div class="dr-row" style="border:1px solid var(--border); padding:8px">
<span class="text-nowrap">.text-nowrap</span>
<span class="text-start dr-fill">.text-start</span>
<span class="text-center dr-fill">.text-center</span>
</div>
<div class="dr-row" style="border:1px solid var(--border); padding:8px">
<span class="text-truncate">A single line long enough that it has to be cut with an ellipsis rather than wrapped</span>
<span class="badge dr-shrink-0">kept</span>
</div>
<p class="text-clamp-3 dr-mb-0" style="border:1px solid var(--border); padding:8px">
.text-clamp-3 cuts after three lines with an ellipsis, however long the text is. This
paragraph is deliberately longer than two lines so that the cut is visible, and it goes on
for a while after that to make the point.
</p>
<p class="text-break dr-mb-0 text-mono" style="border:1px solid var(--border); padding:8px; max-width:200px">
.text-break/pipelines/eu-west/reconciliation-2026-08-03T12-04-31Z.ndjson
</p>
<div class="dr-row">
<span class="text-sm">.text-sm</span>
<span>default</span>
<span class="text-lg">.text-lg</span>
</div>
</div>
Hidden, but not gone
.visually-hidden takes text out of the rendering and leaves it in the
accessibility tree — a name for an icon-only control, a table caption, the target of an
aria-describedby. Add --focusable and it comes back when reached by
keyboard, which is how a skip link works.
<div class="dr-col" style="max-width:420px">
<a class="btn visually-hidden visually-hidden--focusable" href="#utilities">
Focus me with Tab to reveal this link
</a>
<div class="card dr-busy" aria-busy="true">
<div class="card-body">Reloading — this content is stale and not interactive.</div>
</div>
<div class="dr-row">
<span>Reserved space follows:</span>
<span class="dr-invisible">invisible but still occupying its width</span>
</div>
</div>
State, dividers and print
.dr-busy dims a block and stops interaction while it reloads. Pair it with
aria-busy="true", which is the part that is announced.
.dr-invisible is not a replacement for the hidden
attribute: use the attribute, which also removes the element from the accessibility tree and
the tab order. This is only for hiding something that must keep its space.
.dr-disabled is for something unavailable that is not a
<button>, <input> or <a> — those have
the disabled attribute, which is announced and removes them from the tab order,
and this is no substitute for it. Add aria-disabled="true" alongside.
.dr-divider-top and -bottom are a hairline between rows without an
<hr>. .dr-no-print and .dr-print-only are the two
every printed page needs.
<div class="dr-col" style="max-width:460px">
<div class="card dr-busy" aria-busy="true">
<div class="card-body">Reloading — this content is stale and not interactive.</div>
</div>
<div class="dr-row">
<span>Reserved space follows:</span>
<span class="dr-invisible">invisible but still occupying its width</span>
</div>
<div class="dr-col dr-gap-0">
<div class="dr-row dr-divider-bottom" style="padding:8px 0">
<span class="dr-fill">A row with .dr-divider-bottom</span>
<span class="badge">1</span>
</div>
<div class="dr-row dr-divider-bottom" style="padding:8px 0">
<span class="dr-fill">and another</span>
<span class="badge">2</span>
</div>
</div>
<div class="dr-row">
<span class="dr-disabled" aria-disabled="true"><i class="ri-download-2-line"></i> Export (not available)</span>
</div>
<div class="dr-row">
<span class="dr-no-print">.dr-no-print — on screen only</span>
<span class="dr-print-only">.dr-print-only — on paper only</span>
</div>
</div>