The reading page the blog grid has been linking to. Two parts that are used
separately as often as together: .cf-article is the furniture around a
piece of writing — the index, the byline, the tail — and .cf-prose is the
writing itself, set by element rather than by class.
The page
Index on the left, text on the right, both inside the normal container. The index is
sticky below the navigation once there are two columns; below that width it stays where
it is in the source and becomes a block above the text.
This demo is narrower than the real page, and below 1440 px that shows..cf-article splits at 60 rem, measured against its container. On a shipping
page that container is .container; here it is the demo frame, which at a
1280 px viewport is 846 px against the page's 1139 px of content. So the demo below is
a single column at 1280 while the pattern
page is already two — widen the window past 1440 px to see the layout this
component actually ships in.
Widening is not monotonic, though: the documentation sidebar appears at 901 px and
takes 319 px out of this column in one pixel of viewport, so the frame is
narrower between 901 and roughly 1220 than it is at 900. If the demo looks
wrong in that band, that is the chrome around it, not the component.
The frame is not padded out to hide any of this: a demo that lies about its width
documents a component nobody has.
Daniel Tremer
CEO & Managing Partner
Ein digitaler Zwilling ist keine 3D-Animation einer Anlage. Er ist ein
Datenmodell, das mit der Anlage mitläuft.
Was ein Zwilling wirklich ist
In Ausschreibungen steht meistens „digitaler Zwilling“, gemeint ist aber eines
von drei sehr verschiedenen Dingen: ein Geometriemodell, eine Simulation, oder
ein laufendes Abbild des Betriebszustands. Nur das dritte verdient den Namen,
und der Unterschied ist keine Wortklauberei, sondern eine Frage der
Architektur.
Das Datenfundament
Bevor irgendetwas simuliert wird, muss die Telemetrie stimmen:
Zeitstempel ohne Zeitzone. Zwei Steuerungen, zwei Auslegungen von timestamp.
Signale ohne Einheit. Ein Wert von 3,2 ist erst dann eine Messung, wenn irgendwo steht, wovon.
Lücken, die als Nullen ankommen. Der teuerste Fehler, weil er wie ein Messwert aussieht.
Faustregel
Wenn sich der Zustand einer Anlage nicht aus dem Rohdatenstrom rekonstruieren
lässt, rekonstruiert ihn auch kein Zwilling.
Everything below is bare HTML inside a single .cf-prose wrapper. No element
in it carries a class, because article bodies come out of an editor and an editor does
not add classes. Anything that needs a class per paragraph is a system that gets
bypassed the first time someone is in a hurry.
Heading 2 opens on a hairline
Body copy is 16 px here, one step above the system's 14 px. Running text that is read
for minutes at a time gets the larger size; copy that supports a layout does not.
A link inside prose is underlined at all times.
Heading 3 has no rule
The measure is one grid track shared by every text element, so a figure can run
the full width of the column while every line of type stays inside one 66 ch band.
Heading 4 is a mono label
An unordered marker is a 6 px contour square, never a filled dot.
It hangs outside the column so the left edge of the copy stays straight.
An ordered marker is a mono ordinal, zero-padded.
Numbers are tabular, so the column does not shift at ten.
A figure runs the full column width. The caption is mono, under a lighter rule.
Anmerkung
A note is sunken, not outlined, and it is opened by its tag rather than marked by a bar beside it. It reads as a step back from the page rather than as one more box on it.
Wir managen Lösungen, nicht ausschließlich Projekte.
— Aus unseren Werten, Über uns
Ruled rows, no vertical lines, tabular numbers
Betriebsart
Versatz
Geeignet für
Batch
1–24 h
Berichte, Abrechnung
Micro-Batch
1–5 min
Zustandsüberwachung
Streaming
< 1 s
Regelung, Netzstabilität
SELECT asset_id, avg(value)
FROM telemetry
WHERE ts >= now() - interval '15 minutes'
A rule is the finest of the four sanctioned dashes — a break in the argument, not a divider between sections.
<divclass="cf-prose">
<pclass="cf-article__lead">Der Einstieg in den Text.</p>
<h2id="datenfundament">Das Datenfundament</h2>
<p>Fließtext mit einem <ahref="/ueber-uns">Link</a>.</p>
<ul><li>Ein Punkt.</li></ul>
<figure>
<svgrole="img"aria-label="…">…</svg>
<figcaption>Bildunterschrift.</figcaption>
</figure>
<!-- the source is NOT part of the quote, so it sits outside it -->
<figureclass="cf-quote">
<blockquotecite="https://…">Wir bauen Code statt PowerPoints.</blockquote>
<figcaption>— Aus unseren Werten</figcaption>
</figure>
<!-- the tag names the aside and carries its rule; one word, plain text -->
<divclass="cf-prose__note">
<pclass="cf-prose__note-tag">Faustregel</p>
<p>…</p>
</div>
<!-- the scroll box is focusable and named by the caption — see Components · Table -->
<divclass="cf-prose__table-scroll"tabindex="0"role="region"aria-labelledby="versatz-caption">
<table><captionid="versatz-caption">…</caption>…</table>
</div>
</div>
Anatomy
Class
Element
Does
.cf-article
div
Two-column grid: rail and body. One column until the container reaches 60 rem.
.cf-article__rail
div
The index. Sticky below the nav in the two-column state.
.cf-article__toc-title
p
Mono label on a hairline. Names the <nav> via aria-labelledby.
.cf-article__toc
ol
Numbered index, counters zero-padded to two digits.
.cf-article__body
div
Byline, prose, tail.
.cf-article__byline
div
Round avatar plus .cf-person__name / __role.
.cf-article__lead
p
The standfirst. One size up, one step back in colour.
.cf-article__tail
div
Tags left, the way back right, on a full rule.
.cf-article__tag
a / li
Mono chip, 1 px contour, square.
.cf-prose
div
Running text. Styles bare elements; sets 16 px and the 66 ch measure as a grid track.
.cf-prose__note
div
An aside inside the argument. Sunken surface, no contour.
.cf-prose__note-tag
p
Opens the aside: mono label, hairline running out of it to the far edge. One word, plain text.
.cf-prose__table-scroll
div
Wrapper that lets a wide table scroll instead of pushing the page. Same drawing as .cf-table__scroll, and it needs the same three attributes to be keyboard-operable. → Components · Table
.cf-quote
figure
Quote plus attribution. Usable outside .cf-prose.
.cf-figure--inline
figure
Keeps a figure inside the reading column instead of the full width.
Size and measure
Two numbers in this component are decisions rather than defaults, and both are worth
knowing before they get changed.
Decision
Value
Why
Prose size
16 px (--text-lg)
The system's body copy is 14 px, which is right for copy that supports a layout. Text read for minutes at a time steps up. The system already does this — .cf-value-row__body goes to 16 px once it has the room — so prose only makes the move explicit.
Measure
53 ch (--measure-prose)
The readable band for running text is 45–90 characters. ch is the width of the font's zero, and Geist's zero is a wide 0.6875 em — so 53 ch holds 77–80 measured characters of German body text, not 53. The token was 66 ch, which drew 90 characters at a 768 viewport and 100 at 1280: past the band. --measure stays at 56 ch for everything that is not an article.
The measure is a grid track, not a max-width per element.ch is font-relative, so 53ch declared on an h2
resolves against that heading's 32 px and lands at 1166 px, while the paragraph beneath
it resolves the same token at 16 px and lands at 583 px. Every element would get its own
column and the headings would overhang the text they head. Declaring the track once on
.cf-prose resolves 53ch once — at the prose's own size — and
every child sits in the same 583 px column. The second track takes the remainder, which
is what lets a figure span both and step outside the type. A figure that should stay in
the column gets .cf-figure--inline.
Accessibility
One <h1>, in the page header; every section of the body is an <h2>. Levels are never skipped — the index is built from the h2s.
The index is a <nav> named by its own title through aria-labelledby, so a screen reader can tell it apart from the site navigation on the same page.
Headings carry scroll-margin-top for the sticky navigation. Without it an anchor lands underneath the bar.
Links in prose are underlined at all times — colour never carries a link on its own.
The attribution of a quote sits in a figcaption outside the blockquote. Inside it, the source reads as part of what was said. No aria-label on the figure: NVDA already announces the quote, and a label only doubles it.
The byline avatar is decorative — alt="" — because the name is right beside it as text.
Every figure that is a drawing carries role="img" and an aria-label describing what it shows, not what it is.
Dates are <time datetime="YYYY-MM-DD">, so the machine-readable value does not depend on the German format shown.
Wide tables scroll inside .cf-prose__table-scroll; the page body never scrolls sideways.
The sticky rail needs no script and gets none. There is no scroll-spy and
no aria-current that updates as you read: it would need JavaScript, and the
system ships exactly one script. An index that does not follow along is still an index.
Rules
Do
Give every h2 an id and put it in the index — five to eight entries.
Let figures run the full column width; keep type in the measure.
Use the note for an aside the reader may skip, and keep it to three or four lines.
End on the tail: what this was filed under, and one way back.
Don't
No second gradient headline. The page's foil moment is the footer title.
No lime inside the text. Lime is light, and an article already spends its one moment in the figure or the footer.
Don't drop classes onto the elements inside .cf-prose — if it needs a class, it does not belong in an article body.
No index for a piece under four sections. A three-item index is furniture, not navigation.
The full page
patterns/blog-artikel.html is the whole thing:
navigation, page header, index, body, tail, a “Weiterlesen” row built from the blog grid's
own cell, and the footer.