Skip to main content

Content

Blocks for things people read

Editorial and data-presentation components. Two rules run through all of them: a picture of data is always accompanied by the data, and a caption is never the same thing as an alternative text.


Quote

A <figure> containing a <blockquote> and a <figcaption>. The attribution sits outside the quotation, because it is not part of what was said.

“Art and technology — a new unity.”
— Walter Gropius, 1923

Dessau DS takes its conceptual bearing from exactly this idea — design, craft, systems and production treated as one problem rather than four. Not from the visual language: there is no primary-colour geometry here, and no historical pastiche. The influence shows up as clarity, reduction and systems thinking.

Teaser

Facts

A short list of figures supporting what the surrounding text claims. A description list, because each figure has a label.

Components
38
Patterns
11
Contrast pairs verified
148
Runtime dependencies
0

A description list, so each figure is programmatically paired with its label. The number is visually above the label but second in the DOM, because <dt> must precede <dd>.

Call to action

One block, one action, named for what happens next rather than “Submit”.

Start a product with Dessau DS

One stylesheet, two scripts, no build step. Copy the consumer agent template into your repository and you are set up.

Read the setup guide

Download

Format and size are in the visible text, not in a title. Someone on a metered connection needs to know it is a 14 MB PDF before tapping it.

Key-value list

Labelled values in a description list, so the pairing is available to assistive technology and not merely visual.

Reference
7K4M-92QX
Submitted
1. August 2026, 14:30 Uhr
Amount
1.234,56 €
Share
19,5 %
Status
Approved

The term column is fit-content(40%): it takes what it needs up to two fifths of the container and wraps beyond that. As max-content it was as wide as its longest term, so one compound noun made the pair wider than a phone — and, through the grid track it sits in, the page with it.

German formats by default: comma as the decimal separator, point as the thousands separator, 1. August 2026, 24-hour time with “Uhr”. Produced by DDS.format over Intl — see agent/ux-writing.md for the English variant.

Data list

The row-based alternative to a table, for when nothing is being compared down a column.

  • Ilva Bergström Owner · last active vor 2 Stunden
  • Tomasz Wierzbicki Reviewer · last active gestern
  • Nadja Öztürk Viewer · invitation pending

Three buttons all called “Manage access” are three identical controls to a screen-reader user. In a real product each needs the person’s name in its accessible name.

Specifications

A yes/no feature list where the answer carries an icon and a word, never a colour alone.

Specifications

Bauzeit
1925–1926
Architekt
Walter Gropius
Bruttogeschossfläche
2.610 m²
Geschosse
3 bis 5
Konstruktion
Stahlbetonskelett mit Vorhangfassade
Denkmalstatus
UNESCO-Welterbe seit 1996

Specifications or a key-value list?

Both are a <dl>. The difference is what the reader is doing. A key-value list is for a handful of pairs being read — a summary, a review step before submitting. This is for a longer, homogeneous list someone is looking a value up in, which is why every row is separated and the label is tied to its value with a leader.

The leader is generated content, so it is not in the accessibility tree. A row of literal dots would be read out, one per dot.

Chip

A badge is read; a chip is operated. A chip that removes a filter names what it removes.

Status: Approved Owner: Ilva Bergström Archived included
Filter by status

Chart

The rule is absolute: every chart is accompanied by the same data as a real table. The table may be visually hidden, but it must exist — it is the only representation that can be read cell by cell, copied and translated. The bars are aria-hidden.

Documents per project
Documents per project
ProjectDocuments
Harbour1.204
Cycle bridge471
Paving survey47
Consultation8

The label column is capped at a share of the row and wraps beyond it, for the same reason as the key-value list. The value keeps its natural width — it is a number, and a number that wraps is unreadable.

One grid, not one per row. The rows are subgrid, so the widest label and the longest number size a column that every row then shares. Measured per row instead, four labels of four lengths give four different starting points — and comparing lengths that do not share an origin is the one thing the eye cannot do, which is the only thing a bar chart is for.

Share of projects approved
Share of projects approved
Approved68 %
Not approved32 %

A donut is only worth using for one value against a whole. For comparing categories, bars are read more accurately — angle is much harder to judge than length.

Progress ring

A single proportion drawn as a ring. The figure is decorative; the number beside it is the data.

Progress ring

72 %

In place, beside the thing it is about — which is the only reason to choose a ring over a bar:

  • bebauungsplan-nord.pdf 4,2 MB · fertig
  • lageplan-2026.dwg 46 % 46 % von 18,7 MB

The circle communicates nothing on its own

The ring is aria-hidden, exactly like the charts. A screen reader gets nothing from an angle, so the value has to exist as text or on a real <progress> — as in the second row above. A ring with no textual value is a decoration that looks like information.

Use a bar wherever one fits. This exists for the case where it does not: a table cell, a dense dashboard, beside a filename. It is drawn with a conic gradient and a radial mask, so it sits on any surface without needing an opaque inner circle matched to the background.

Figure

alt replaces the image for someone who cannot see it; a caption is supplementary information everyone gets. Repeating one as the other means the same sentence is announced twice.

A schematic of the four cascade layers, stacked from foundation at the bottom to utilities at the top.
The cascade layer order. Unlayered product CSS always wins over every layer shown here.

Text-media

A block of text beside an image — the recurring content block of a CMS page. Three layouts above 40rem of container width; below it all three stack, and the media is on top.

Text and media, responsive by container query

The media is written first anyway

Wide, the image sits on the trailing edge and the text reads from the leading one. Narrow, this block stacks with the image on top — and it is on top because it is first in the markup, not because order put it there.

Take the width down to 375 and back. The only thing that moves is the column the image is placed in.

The same markup, one class apart

Wide, the image keeps the leading column — which is where the source order already puts it. The rule is written out regardless: the column a block means to be in should not have to be derived from the position of a <div>.

Narrow, it is indistinguishable from the variant before it. That is the intended result, not a coincidence.

One column at every width

The lead-image layout. It never becomes two columns, because half the width would be a smaller picture rather than a different one.

The text is capped at the reading measure instead. Full width is not a measure: at 1280 a paragraph would run past a hundred characters a line.

Drag the width buttons. Because the layout comes from @container rather than @media, the stage width is what the component measures — which is also why it works correctly inside a narrow panel or a dialog. The width survives switching layout, so the three can be compared at the same one.

At 375: all three are the same layout — media, then text. The trailing variant included. A phone shows the illustration with its heading rather than four paragraphs later, and because the media element is first in the source, that is what a screen reader reads out too (WCAG 1.3.2).

Consent embed

A third-party frame contacts that provider — and can set cookies and log an IP address — the moment the page loads, before the visitor has agreed to anything. Deferring the load until a deliberate click means no request leaves the page unasked.

Kasalla — Jröne Papageie

Loading this video contacts YouTube, which may set cookies and record your IP address. Nothing has been sent yet.

Open on YouTube instead (opens in a new tab)

youtube-nocookie.com is used rather than youtube.com: it is the privacy-preserving embed host, and there is no reason to prefer the other one. autoplay is deliberately not in the allow list.

Instagram post — Bauhaus Dessau

Loading this post contacts Instagram, which may set cookies and record your IP address. Nothing has been sent yet.

Open on Instagram instead (opens in a new tab)

.dds-embed-portrait: a social post is not 16:9, and it takes a fixed height rather than an aspect ratio. The provider’s own frame renders at a fixed intrinsic height — header, image, action bar, caption — so a ratio box makes it grow its own browser scrollbar inside ours, which we can neither style nor remove.

Bauhaus Dessau
Gropiusallee 38, 06846 Dessau-Roßlau

Loading this map contacts Google, which may set cookies and record your IP address. Nothing has been sent yet.

Open in Google Maps instead (opens in a new tab)

A map embed needs the address in real text beside it, not only inside the frame. The frame is an image to a screen reader, and it does not load at all until consent is given — so the address has to be readable either way.

Do

  • Name the provider and say what loading it means.
  • Always offer a plain link to the content on the provider’s site.
  • Give the <iframe> a title.
  • Move focus into the frame once it loads — the button is gone.

Don’t

  • Remember the consent as a component side effect.
  • Include autoplay in allow.
  • Ship the <iframe> in the markup and hide it with CSS — it still loads.
  • Treat this as a substitute for a real consent record.

Media

Transcript — Example audio track

[00:00] This recording is four seconds of silence. It exists so the player has something to play, and so this transcript has something to transcribe.

[00:04] End of recording.

Native controls: keyboard operable, screen-reader accessible, and they honour the platform’s own accessibility settings. Video with speech additionally requires a <track kind="captions"> — that is WCAG 1.2.2, not a nice-to-have.

The transcript is required, not recommended (WCAG 2.2 1.2.1). Audio-only content without one is unusable for a deaf user and unfindable for everyone — a search engine and a page search both read text, and neither reads a waveform. It is the most commonly skipped requirement in the standard, because the work is producing the transcript rather than writing the markup, so check-reference.mjs fails on a <video> or <audio> with no [data-dds-transcript] beside it.

Avatar

A person, as an image or their initials. Decorative beside their name, and never the only way a person is identified.

and 4 others

Initials are aria-hidden. “IB” read aloud tells nobody anything — the name belongs in real text beside the avatar or in a visually hidden span.

Byline

Who wrote it and when, compact, for the top of an article.

Byline — compact, for the top of an article

Byline — boxed, for the end of one

“Email” is not a link name

A screen-reader user can list every link on a page and hear them out of context. In that list, “Email” appears with no indication of whose — and on an article with three contributors, three identical entries. The name goes in the link text: “E-Mail an Ilva Bergström”.

The date is a <time> with a machine -readable datetime. The visible form is written for the reader's locale; the attribute is what a machine reads, and the two do not have to look alike.