CSS for a readable interface
CSS does not decide the whole experience. HTML carries structure and meaning; CSS makes that structure perceptible across sizes, densities and preferences. Good CSS gives a clear rule to a real problem and lets content breathe.
Flow before positioning
Start with normal flow, Flexbox and Grid. Absolute positioning is useful for a local relationship — an icon inside a field, for example — but it should not become the page plan.
.session {
width: min(100% - 2rem, 72rem);
margin-inline: auto;
}
.session__content {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(16rem, 22rem);
gap: var(--sl-space-8);
align-items: start;
}
@media (max-width: 48rem) {
.session__content {
grid-template-columns: 1fr;
}
}
minmax(0, 1fr) prevents long content from forcing a column to overflow. The
media query changes the structure when two columns can no longer preserve
readability, rather than shrinking the type until it becomes uncomfortable.
Tokens name decisions
A value copied several times becomes difficult to evolve. A token says why it exists:
:root {
--ui-space-1: 0.25rem;
--ui-space-2: 0.5rem;
--ui-space-4: 1rem;
--ui-color-surface: #0b141e;
--ui-color-text: #ffffff;
--ui-color-muted: rgba(255, 255, 255, 0.72);
--ui-color-accent: #00d1ff;
--ui-radius-control: 0.75rem;
}
DevLab101 shared tokens start with --sl-*. Use the values in
src/design-system/tokens.css before creating another one. Prefer a semantic
name such as “primary action accent” to --blue-3.
A consistent spacing scale
Space groups or separates decisions: short space between label and field, medium space inside a group, large space between sections and page space around the main content. Do not fix every offset with a new margin. If a distance does not express a content relationship, check the component or hierarchy first.
Typography and reading measure
Define body size, line height and line length explicitly. ch can limit a
paragraph without imposing a fixed width on every screen.
.lesson-copy {
max-inline-size: 70ch;
color: var(--sl-text-secondary);
font-size: 1rem;
line-height: 1.7;
}
.lesson-copy h1,
.lesson-copy h2,
.lesson-copy h3 {
color: var(--sl-text-primary);
line-height: 1.15;
}
Do not hide focus with outline: none without a visible replacement. Important
information should not exist only in capitalization, low opacity or italics.
States belong to the component
A button usually needs idle, hover, focus-visible, pressed, loading, success, error and disabled states according to its contract.
.action {
min-block-size: var(--sl-control-height);
padding-inline: var(--sl-space-4);
color: var(--sl-accent-contrast);
border: 1px solid var(--sl-accent);
border-radius: var(--sl-radius);
background: var(--sl-accent);
}
.action:focus-visible {
outline: 3px solid var(--sl-focus);
outline-offset: 3px;
}
.action:disabled,
.action[aria-disabled='true'] {
cursor: not-allowed;
opacity: 0.45;
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
scroll-behavior: auto !important;
transition-duration: 0.01ms !important;
animation-duration: 0.01ms !important;
}
}
aria-disabled does not automatically prevent a click. If you use it, also
implement the behaviour; a real disabled attribute is often safer.
Forms and errors
An error must be identifiable without color alone and connected to its field. CSS can reinforce the distinction; it cannot invent the message.
<label for="answer">Your answer</label>
<input id="answer" aria-describedby="answer-help answer-error"
aria-invalid="true">
<p id="answer-help">Two sentences are enough.</p>
<p id="answer-error">Describe what you observed.</p>
Keep input when a request fails. A network error should not turn into an empty form or a blocked screen.
Responsive and unpredictable content
Test at 320 px, 768 px and 1440 px, with a title twice as long, with the other
locale, at 200% zoom and with larger text. Prefer min(), max(), clamp(),
minmax() and logical dimensions (inline-size, block-size,
margin-inline) to fixed heights and precise coordinates. A card can grow with
its copy; content should not be clipped to preserve a screenshot.
A reset is not a strategy
A small reset can harmonise margins, but a universal reset that removes native button, list, focus and form behaviour forces the rest of the product to rebuild it. Start with semantic HTML and neutralise only a documented design decision.
Five CSS review questions
- What content relationship does this rule make visible?
- Is it a token or a genuinely necessary exception?
- What happens with a long line, error and focus state?
- Does it hold at 320 px, zoom and in the other locale?
- Does the component remain understandable without color, motion or hover?
FlashLearning workshop
For a review-session screen, write three rules before writing CSS:
- the width the question text should keep;
- the action that must stay visible on mobile;
- how focus and error remain perceptible.
One possible answer
Limit the question to a readable measure, keep “Reveal answer” in the main flow and stack actions below 48 rem. Give focus a visible border, connect the error to the field and use text or an icon in addition to color. Tokens carry space, radius and accent so cards and buttons share one grammar.
Reliable sources
- MDN — CSS layout
- MDN — responsive design
- W3C — Understanding focus appearance
- GOV.UK Design System — styles and components
Connect these rules to meaning in Design an interface people can understand, then open the interactive UI/UX path.