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.

No lime down here. On the shipped 404 page the screen's one lime moment is the lit socket in the object — the place the missing thing should be. So the primary action is .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.

Where this belongs. News overview renders a grid of eight; a listing left with nothing to draw renders this in place of the grid rather than an empty container. The topic filter cannot reach that state — a topic no post carries gets no chip and no page — so on this route it is the search that arrives here. A page number out of range is the other case, and that one is a real 404 — the address is wrong, not the answer.

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

PartValue
Block widthuncapped — every part that is read as prose carries its own measure, and a cap on the block would cap the routes too
Status labelGeist Mono, 11 px, uppercase, --text-secondary, tabular figures
TitlePublica 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
Body14 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)
Actionsa wrapping row, --space-3 (12 px) apart; solid first, ghost second
Routes rule, top1 px --border-strong — the page's hairline
Routes rule, per row1 px --border-default, going to full ink on hover
Route row12 rem label column, fluid description, arrow right; --space-5 (20 px) top and bottom
Route foldbelow 34 rem of the component the label moves above its description
Object frame640 × 440, capped at 34 rem, centred when it drops under the copy
Page gridone column; two equal columns from 62 rem, routes spanning both
Why the row rules are not full ink. There are up to five of them in a column and five black hairlines is a fence. The rules sit at --border-default and the row being pointed at takes the ink — which is the same reason the pagination has no contour at rest.

Behaviour

Accessibility

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.