Components
Error & Empty State
The other terminal state of a listing, which is why it lives next to the pagination: one closes a page that has content, this one stands in for a page that has none. Three cases share one shape — the address that resolves to nothing (404 / 410), the address that resolves and fails (500), and the filter that matched nothing. Links and copy, no script.
The block
A mono status label, a sentence, two or three lines of copy, and the way out. The status code is a label, not a headline: a 96 px “404” is the one thing every error page does and it says nothing to the person who cannot find the page — the code is for whoever reads the logs. The headline is a sentence in plain German.
Fehler 404
Diese Seite gibt es nicht.
Entweder hat sich in der Adresse ein Zeichen verschoben, oder die Seite ist umgezogen. Der Rest der Website steht — unten stehen die Bereiche, in denen das Gesuchte am ehesten liegt.
.cf-btn--solid, the black plate whose label is the foil,
the same “this is the one” move the pagination's current page makes.
.cf-btn--primary is right here only on an error page that carries no
object. → Colour
The way out
A dead end needs somewhere to go. The routes list is that somewhere: hairline rows, a mono label, one line of copy, an arrow. Contour before fill — at rest a route is two rules and some type, and the only things that move on hover are the arrow and the ink. Rows also stack without a media query, which a card grid does not.
Three to five rows. The list does not repeat whatever the primary button already offers — a dead end needs exits, not the same exit twice. On the 404 page the button is the home page, so the home page is not in the list.
Inline — the filter that matched nothing
The same block at section scale, inside the ruled container the results would have
filled. It draws no rule of its own, because the container's edges are already the box,
and its title is never an <h1> — the page has a title and it is still
correct. The distinction that matters: no results is not an error. The address
worked, the server worked, the answer is simply empty, so the copy says what to change
rather than apologising.
0 Treffer
Zu diesem Thema gibt es noch nichts.
Der Filter Energie liefert für 2026 keine Beiträge. Ein anderes Thema oder das vollständige Archiv führen weiter.
The whole page
--page puts the object beside the copy and spans the routes underneath.
It deliberately does not sit under a
page header: an error page has no title / rule / meta
triple to draw, because the error is the title, and there is no long headline
for an object to sit behind. So the object comes out from behind the type and stands
next to it, on a grid that folds on its own — where
.cf-page-header__figure is absolutely positioned and has to be switched off
under 900 px. The full-ink rule that opens the routes list is the page's hairline,
drawn once, by the thing it belongs to.
The object is a row of identical units on the isometric ground plane with one of them
missing. Its empty socket is the only lit thing in the frame — the place where something
should be and is not — and the missing unit itself is drawn dashed, leaving the frame
upward. Built on u = 60 from the origin
(200, 340); the row is level because it runs on
e1 − e2 = (4u, 0), and the ramp sits at 63.43° in
the near rake with the oklab waypoint at 0.061.
→ Illustration
See it whole: patterns/404.html.
Markup
<div class="cf-error cf-error--page"> <div class="cf-error__lead"> <p class="cf-error__code">Fehler 404</p> <h1 class="cf-error__title">Diese Seite gibt es nicht.</h1> <p class="cf-error__body">Entweder hat sich in der Adresse …</p> <div class="cf-error__actions"> <a class="cf-btn cf-btn--solid" href="/">Zur Startseite <svg class="cf-arrow" aria-hidden="true"><use href="#cf-arrow"></use></svg> </a> <a class="cf-btn cf-btn--ghost" href="/kontakt">Defekten Link melden …</a> </div> </div> <!-- optional: one isometric object, --page only --> <svg class="cf-error__figure cf-iso" viewBox="0 0 640 440" aria-hidden="true">…</svg> <nav class="cf-error__routes" aria-label="Wege von hier"> <ul class="cf-error__routes-list" role="list"> <li> <a class="cf-error__route" href="/ueber-uns"> <span class="cf-error__route-label">Über uns</span> <span class="cf-error__route-desc">Das Team, die Werte und wie wir arbeiten.</span> <svg class="cf-arrow" aria-hidden="true"><use href="#cf-arrow"></use></svg> </a> </li> </ul> </nav> </div>
Drop --page, the figure and the __lead wrapper for the plain
block; add --inline for the empty state. __lead exists only so
the page grid has one box to put in its first column — without it the copy and the
object would need row spans that break the moment a paragraph is added.
Anatomy
| Part | Value |
|---|---|
| Block width | uncapped — every part that is read as prose carries its own measure, and a cap on the block would cap the routes too |
| Status label | Geist Mono, 11 px, uppercase, --text-secondary, tabular figures |
| Title | Publica Sans bold, --text-display-2 — --text-display-1 with --page, --text-h3 with --inline |
| Title measure | --measure-tight (26 ch), so a sentence breaks where the eye expects |
| Body | 14 px, --leading-relaxed, --measure (56 ch), --text-secondary |
| Air: label → title | --space-4 (16 px) |
| Air: title → body | --space-6 (24 px) |
| Air: body → actions | --space-8 (32 px) |
| Actions | a wrapping row, --space-3 (12 px) apart; solid first, ghost second |
| Routes rule, top | 1 px --border-strong — the page's hairline |
| Routes rule, per row | 1 px --border-default, going to full ink on hover |
| Route row | 12 rem label column, fluid description, arrow right; --space-5 (20 px) top and bottom |
| Route fold | below 34 rem of the component the label moves above its description |
| Object frame | 640 × 440, capped at 34 rem, centred when it drops under the copy |
| Page grid | one column; two equal columns from 62 rem, routes spanning both |
--border-default and the row being pointed at takes the ink — which is the
same reason the pagination has no contour at rest.
Behaviour
-
The status code goes in the response, not only on the page. A missing
address must answer
404, one that is deliberately gone410, a failure500. Serving this template with200 OKis a soft 404: search engines index the dead address, keep crawling it, and the reader gets a page that says one thing while the protocol says another. The four retired topic pages — Maschinenbau, Energie, Dienstleistungen, Experten — are301s to their new homes, not 404s; a redirect that is available is always better than an error page that is polite. - The page title carries the code. “Seite nicht gefunden (404)”. It is what makes error pages separable in an analytics report without a tracking parameter, and it is the first thing a screen reader announces on arrival.
-
Nothing is hidden and nothing animates but the arrow. The rules and
the ink cross-fade in 120 ms, the arrow leans 4 px in 240 ms — the same nudge
.cf-btngives it. Underprefers-reduced-motionevery duration token drops to 1 ms, so all three become instant without a rule of their own. -
The object is optional and never load-bearing. It is
aria-hiddendecoration built in the ordinary illustration language, so it assembles on a view timeline where one is supported and is simply drawn finished where it is not — including on paper and under reduced motion. - No search box. The site has no search. Offering one on the one page where a reader is already lost would be the second dead end; four named routes are the honest version of the same help.
Accessibility
- The title is the page's
<h1>on a 404, and never an<h1>inline — an empty grid interrupts a section, it does not retitle the page. - The routes are a
<nav>with anaria-labelthat says what they are for, so a page with two navigations stays unambiguous.role="list"is on the<ul>becauselist-style: noneremoves the role in Safari. - Each route is one link containing label, description and arrow — not a link plus a second link. One target, one announcement, and the label reads first because it is first in the source.
- Rows are 20 px of padding on a 14 px line — 61 px tall, measured — well past SC 2.5.8's 24 px minimum, and the whole row is the target rather than the label alone.
- Hover moves the rule to full ink and travels the arrow 4 px, and focus does exactly the same — on top of the system's own ring, which wraps the whole row. This used to be hover-only, on the grounds that the ring was already enough. It is not the same claim: the ring says where you are, the rule and the arrow say this row goes somewhere, and a reader without a pointer was told only the first.
- Contrast on the page wash:
--text-secondaryis 5.9:1, the title and the route labels are full ink at 13.5:1, the row rule is a non-text contour that carries no state on its own.--text-mutedis never used here —#919191on#CFCFCFis 2.0:1. - The status label is decorative typography but not decorative content — “Fehler 404” is information, so it is real text and is not
aria-hidden, unlike the section header's counter. - Colour is never the only signal: every state here is also a rule weight or a position.
Rules
Do
- Say what happened in one plain sentence, and be apologetic without blaming the reader.
- Keep the site's own nav, footer and wash. A page that looks foreign is a second dead end — and the footer is what keeps the Impressum reachable from every address (DDG § 5).
- Give three to five named routes, each with a line saying what is behind it.
- Match the HTTP status to the page: 404 missing, 410 gone, 500 failed, 200 for an empty filter.
- Redirect instead, whenever there is somewhere honest to redirect to.
Don't
- Don't set the status code as the headline. It is a label; the sentence is the headline.
- Don't be funny. Nobody reaches this page in a good mood, and a joke that misses reads as indifference.
- Don't spend lime here when the object already has it — one lime moment per screen.
- Don't use the inline variant to report an error. An empty result is not a failure, and calling it one teaches the reader to distrust the filter.
- Don't bounce the reader to the home page automatically. A redirect they did not ask for costs them the address they can still fix.