Layout utilities
Hypermedia Components ships a handful of layout utilities in the
hc.utilities cascade layer. They are the responsive building blocks the
rest of the system leans on — including the
hc-shell application shell.
They are deliberately few and semantic. This is not a utility-first
framework: there are no spacing scales sprayed across class names, no
md: / lg: breakpoint prefixes. Each utility is intrinsically
responsive — it adapts to the space it is given through flex-wrap,
grid auto-fill, or content-based flex ratios, with no media queries.
The same class behaves correctly in a full page, a dialog, a card, or a
sidebar, because it responds to its container, not the viewport.
Import
Section titled “Import”The utilities are in the full stylesheet already. For granular setups, import just this layer on top of the core:
import '@hypermedia-components/core/css/core'; // tokens + baseimport '@hypermedia-components/core/css/utilities'; // this layerBecause they live in the last cascade layer, your own application CSS overrides them without specificity fights.
Tuning knobs
Section titled “Tuning knobs”Every utility exposes one or two local custom properties with token-based fallbacks. Set them inline or in a wrapper rule — never add per-element variant classes:
<ul class="hc-cluster" style="--hc-cluster-gap: var(--hc-space-4)">…</ul>.hc-stack — vertical rhythm
Section titled “.hc-stack — vertical rhythm”Children laid out in a column with one consistent gap. Knob:
--hc-stack-gap (default --hc-space-3).
<div class="hc-stack"> <div>First</div> <div>Second</div> <div>Third</div></div>.hc-cluster — wrapping row
Section titled “.hc-cluster — wrapping row”A horizontal group that wraps when it runs out of room — button rows,
tag lists, meta bars, toolbars. Knobs: --hc-cluster-gap (default
--hc-space-2), --hc-cluster-align (default center).
<div class="hc-cluster"> <button class="hc-button">Save</button> <button class="hc-button" data-variant="secondary">Duplicate</button> <button class="hc-button" data-variant="ghost">Archive</button> <button class="hc-button" data-variant="error">Delete</button></div>.hc-grid — responsive grid
Section titled “.hc-grid — responsive grid”As many equal columns as fit at a minimum track width, then it wraps —
no breakpoints. Knobs: --hc-grid-min (default 16rem), --hc-grid-gap
(default --hc-space-4). The built-in min(…, 100%) guard stops a wide
track from overflowing a narrow container.
<div class="hc-grid" style="--hc-grid-min: 16rem;"> <article class="hc-card">…</article> <article class="hc-card">…</article> <!-- …as many as you like --></div>Swap auto-fill for auto-fit (by setting
grid-template-columns yourself) when you want a few items to stretch
and fill the row instead of leaving empty tracks.
Forms: data-align="start" and data-span="full"
Section titled “Forms: data-align="start" and data-span="full"”Equal-height cards want the default stretch. A form does not: a three-row textarea beside a one-line input stretches its neighbour into a tall empty box. Two opt-ins cover it:
<div class="hc-grid" data-align="start" style="--hc-grid-min: 18rem;"> <div class="hc-field">…</div>
<!-- A row of its own, whatever the track count. --> <div class="hc-field" data-span="full"> <label class="hc-field__label" for="notes">Notes</label> <textarea class="hc-input" id="notes" rows="3"></textarea> </div></div>data-align="start"on the grid — items align to the top of their row instead of stretching. Opt-in, because equal-height cards are the other common use of this grid.data-span="full"on an item — it takes the whole row.auto-fillmeans which items pair up changes with width, so anything that must not share a row (a textarea, a date range, a radio group) says so itself rather than depending on a pairing that only holds at one width.
.hc-container — centred content well
Section titled “.hc-container — centred content well”A centred column with a maximum line length and inline padding. Knobs:
--hc-container-max (default 72rem), --hc-container-pad (default
--hc-space-4).
<div class="hc-container"> <!-- page content, capped and centred --></div>.hc-sidebar — sidebar + main
Section titled “.hc-sidebar — sidebar + main”A two-part layout where the first child is the sidebar and the second
is the main region. The main region keeps a minimum width; when it can no
longer hold it, the sidebar wraps onto its own row. Pure flexbox, so it
reflows by container width — drop it inside a narrow panel and it
collapses there too, with no viewport media query. Knobs:
--hc-sidebar-width (default 16rem), --hc-sidebar-content-min
(default 60%), --hc-sidebar-gap (default --hc-space-4).
<div class="hc-sidebar"> <nav aria-label="Section">…</nav> <main>…</main></div>.hc-fill — take the remaining space
Section titled “.hc-fill — take the remaining space”The composition every full-height app screen needs, and the one easiest to get wrong: a page column of fixed regions, then one region that takes what is left and lets its own children scroll.
<div class="page"> <!-- flex column, both minimums 0 --> …title, toolbar… <form class="hc-fill"> <!-- every element on the path --> <div class="hc-datagrid hc-fill">…</div> </form></div>.hc-fill is display: flex; flex: 1; flex-direction: column plus
both minimums — and both are load-bearing, one per axis:
- miss
min-block-size: 0and the region grows to fit its content, so the page scrolls and the fixed chrome scrolls away; - miss
min-inline-size: 0and a wide child (amax-contenttable) makes an ancestor the horizontal scrollport, dragging the chrome sideways.
Put it on every element between the column and the filling region,
including a wrapping <form>. On an
hc-datagrid it also
switches --hc-datagrid-max-height from the default 70vh — right for
a grid inside a scrolling page — to 100%, right for a grid that
is the page.
.hc-sr-only — visually hidden
Section titled “.hc-sr-only — visually hidden”Content available to assistive technology but removed from the visual
layout — skip links, labels for icon-only controls. Unlike .hc-hidden,
it stays in the accessibility tree.
<button class="hc-button" type="button"> <svg aria-hidden="true">…</svg> <span class="hc-sr-only">Delete</span></button>.hc-hidden — force-hide
Section titled “.hc-hidden — force-hide”display: none !important. Wins over component rules by layer order, and
the !important also beats unlayered application CSS and inline display.
Use it to toggle visibility from the server (swap the class in/out) or
from htmx, without inline styles.
<div class="hc-hidden">Removed from layout entirely</div>.hc-numeric — tabular figures
Section titled “.hc-numeric — tabular figures”font-variant-numeric: tabular-nums for app-owned numeric surfaces —
stat tiles, KPI values, counters — outside the kit components that
already default to it (table and datagrid cells, form controls,
calendar days, pagination, badges, timeline timestamps). Digits get a
uniform width so values align and don’t jitter when they update; text
is unaffected. Glyphs only — alignment stays the context’s job.
<p> <span class="hc-numeric">42,195</span> rows scanned</p>Related
Section titled “Related”- Responsive design — the container-first strategy these primitives implement.
- Tokens — the
--hc-space-*scale the gaps default to. hc-shell— the application shell, built on these primitives.