⚠️ Design system CSS not found. Check the path in cms/docs.config.js → designSystemPath, then re-run npm run docgen.

Book

Shelf, cover, and contents — the surfaces you navigate a documentation space through

Website / Book
Download .md file
Open .md in new tab
On this page
  • Anatomy
  • Basic usage
  • Using the header slots
  • Using the footer slots
  • Sizes
  • Contents
  • Using the row slots
  • Columns
  • Group headings
  • Contents as disclosures
  • Accessibility
  • Usage rules
  • CSS reference
  • Use in another product

A documentation space is a set of books. The top level is a shelf of covers; opening one shows its contents; opening a line in the contents lands you on a page. The book family gives you the first two of those, as four classes: , , , and .

The row in a contents list is a contents item, not a page — in a book, the thing you click in the contents is an entry and the thing you land on is the page. Naming them apart is what keeps the model legible.

Two components complete the model and live in their own docs: moving sideways between pages is Page Navigation; moving back up is Breadcrumb.


Anatomy

.book-shelf                              ← L0 grid of covers
  .book-shelf-title                      ← optional group heading
  a.book-cover                           ← one closed book
    .book-cover-header
      .book-cover-leading                ← icon, varies per card
      .book-cover-trailing               ← icon, hints at what clicking does
    .book-cover-body
      .book-cover-title
      .book-cover-description
    .book-cover-footer
      .book-cover-byline                 ← "by X"
      .book-cover-meta                   ← metadata

.book-contents                           ← L1 table of contents
  .book-contents-title                   ← optional group heading
  a.book-contents-item                   ← one line in the contents
    .book-contents-item-leading          ← icon, varies per row
    .book-contents-item-body
      .book-contents-item-title
      .book-contents-item-description
      .book-contents-item-meta           ← metadata, under the description
        .book-contents-item-byline       ← "by X", inside meta
    .book-contents-item-trailing         ← icon, hints at what clicking does

details.book-contents-item               ← the same row, opening in place
  summary                                ← the visible row: title + trailing
  .book-contents-item-panel              ← revealed: description + meta

Slots are named on two different principles, because they hold two different kinds of thing:

  • leading / trailing name a position. They frame the surface and hold an icon. Deliberately generic — another product's trailing icon might be a chevron, an arrow, or a download glyph, and the name should not argue with any of them.
  • byline / meta name a content type. They carry text about the item, so naming them by position would say nothing useful about what belongs there.

On a cover, leading and trailing are the header's two ends; the footer's two ends are byline and meta, because what belongs there is text rather than icons. A row has only one line, so leading and trailing are unambiguous, and meta moves inside the body.

Neither icon slot may hold an interactive control. The card is already a link, so a nested button is invalid HTML and unreachable in several screen-reader modes. trailing holds an icon that hints at what clicking does — it is not an action control. A surface that needs a real second action wants a different component.

Every slot is optional. Empty ones are simply left out — the end slot's auto margin holds its side either way, so no spacers are needed.

Engine and instance. The component guarantees the slots. Which icon goes in trailing, and whether it stays the same down a shelf, is an instance decision. By Default keeps it constant, because constancy is what signals "this opens" — but that is a usage rule for this brand, not a property of the component. Another product can decide otherwise without the class names lying.

Note the row has no header or footer strip. A cover is a vertical surface with room for two strips; a row is one horizontal line, so leading starts it and trailing ends it. The cover's separate byline and meta collapse into meta on the row for the same reason.

Row meta sits inside the body, under the description — not as a fourth item on the line. On a cover, meta lives in the footer strip and gets a line of its own; a row has no strip, and leading and trailing both refuse to shrink, so a trailing meta would take its width straight out of the title. Under the description it wraps instead of squeezing.


Basic usage

A shelf holds covers. owns its own grid, so no utility classes are needed alongside it.

Brand Book

Brand identity, values, positioning, and visual guidelines

by Studio

Design System

Tokens, components, and styling patterns

by Erlen
<div class="book-shelf">
  <a href="/brand/" class="book-cover">
    <header class="book-cover-header">
      <div class="book-cover-trailing">
        <div class="svg-icn" data-icon="open-full"><!-- inline svg --></div>
      </div>
    </header>
    <div class="book-cover-body">
      <h3 class="book-cover-title">Brand Book</h3>
      <p class="book-cover-description">Brand identity, values, positioning, and visual guidelines</p>
    </div>
    <footer class="book-cover-footer">
      <span class="book-cover-byline"><em>by</em> Studio</span>
    </footer>
  </a>
  <!-- repeat for each book -->
</div>

Covers share a minimum height so a row of mixed-length titles still reads as a shelf. Resize every cover at once by re-pointing --book-cover-min-height; never override the height on a single card.

For auto-generated covers, the doc generator builds these from cms/*.md frontmatter. Set the byline with the author: frontmatter field, which falls back to Studio when omitted.


Using the header slots

The header is a strip with a start slot and an end slot. holds the open affordance on the right; is the optional left slot that says what this is.

Use leading when a shelf mixes kinds of thing and the title alone doesn't distinguish them — a brand book beside a tool beside a reference. On a shelf of like-for-like sections it is noise, so leave it out.

Brand Book

Identity, values, and visual guidelines

by Studio

Tools

Utilities for ad ops and asset preparation

by Studio
<header class="book-cover-header">
  <div class="book-cover-leading">
    <div class="svg-icn" data-icon="catalog"><!-- inline svg --></div>
  </div>
  <div class="book-cover-trailing">
    <div class="svg-icn" data-icon="open-full"><!-- inline svg --></div>
  </div>
</header>

The leading icon is visible at rest; the trailing icon fades in on hover and focus. That difference is the point — one says what this is, the other says what happens if you click.

A leading icon that only repeats the title needs aria-hidden="true". One that carries information the title doesn't needs an accessible name.


Using the footer slots

Same shape at the bottom: on the left, on the right. Meta is a status slot — a count, a date, an access level — and Tag or Badge sit in it directly.

Design System

Tokens, components, and styling patterns

by Studio 38 pages

Commercial

Rate cards, contracts, and partner terms

by Studio Team only
<footer class="book-cover-footer">
  <span class="book-cover-byline"><em>by</em> Studio</span>
  <span class="book-cover-meta">
    <span class="tag">38 pages</span>
  </span>
</footer>

Keep meta to a short noun. The whole card is one link, so its accessible name is every word inside it — a sentence in the footer makes each cover announce as a paragraph.

A colour-only status fails WCAG 1.4.1. "Team only" in an amber tag is fine; an amber dot on its own is not.


Sizes

data-size re-points height, title size, and content inset together. A short cover with a full-size title reads as a mistake rather than a variant, so the three move as one.

Small

Dense shelves, secondary sections

by Studio

Default

The canonical index cover

by Studio

Large

A landing page's primary route in

by Studio
<a href="#" class="book-cover" data-size="small">…</a>
<a href="#" class="book-cover">…</a>          <!-- default -->
<a href="#" class="book-cover" data-size="large">…</a>
Size Min height Title Title ≤959px Content inset
small 170px
default 250px
large 340px

Each title drops two rungs below 960px, the same step the heading scale takes.

Keep one size per shelf. Uniform height is what makes an index read as a shelf rather than a pile — mixing sizes in one grid is the fastest way to lose that. The demo above breaks the rule only to show the three side by side.


Contents

Inside a book, entries stack as wide horizontal rows rather than tall centred cards. The wrapper draws the outer border; each item draws its own bottom rule.

Colour

Palette, scales, and semantic colour mappings

Typography

Type scale, weights, and text utilities

<div class="book-contents">
  <a href="/website/color.html" class="book-contents-item">
    <div class="book-contents-item-body">
      <h3 class="book-contents-item-title">Colour</h3>
      <p class="book-contents-item-description" data-text-wrap="pretty">Palette, scales, and semantic colour mappings</p>
    </div>
    <div class="book-contents-item-trailing">
      <div class="svg-icn" data-icon="open-full"><!-- inline svg --></div>
    </div>
  </a>
  <!-- repeat for each entry -->
</div>

Using the row slots

A row carries the same slot vocabulary as a cover, but no header or footer strip. A cover is a vertical surface, so it has room for two strips with two ends each. A row is one horizontal line: leading at the start, trailing at the end, and everything else in the body between them.

That is also why the cover's separate byline and meta slots collapse here — on a footer strip they are two ends with two jobs, but a row has no strip, so the byline sits inside meta. Meta takes whatever the row needs to say about itself: an access tag, a byline, a date, or several.

Tone of Voice

How we sound, and the words we avoid

by Studio

Rate Card

Commercial terms and partner pricing

Team only

Iconography

The icon set, and when to request a new one

Updated by Erlen
<a href="/brand/rate-card.html" class="book-contents-item">
  <div class="book-contents-item-leading">
    <div class="svg-icn" data-icon="lock"><!-- inline svg --></div>
  </div>
  <div class="book-contents-item-body">
    <h3 class="book-contents-item-title">Rate Card</h3>
    <p class="book-contents-item-description" data-text-wrap="pretty">Commercial terms and partner pricing</p>
    <span class="book-contents-item-meta">
    <span class="tag" data-color="warning">Team only</span>
  </span>
  </div>
  <div class="book-contents-item-trailing">
    <div class="svg-icn" data-icon="open-full"><!-- inline svg --></div>
  </div>
</a>

Both slots are optional and independent — the third row above has meta but no identity, and mixing them down a list is fine.

Meta wraps rather than competing, so a row tolerates more here than a cover footer does. Identity and action still refuse to shrink, so keep those to a single icon.


Columns

data-cols re-points the column token. Two columns is the default and the canonical index layout — it gives every book room to breathe. Three suits shorter titles and denser sets.

<div class="book-shelf" data-cols="3">…</div>

Both settings collapse to a single column at 768px. The breakpoint belongs to the component rather than the grid utility, so it travels with it into another product.


Group headings

and label a group within a longer index. They carry the eyebrow treatment without depending on the utility, so a product can restyle them on their own.

<h2 class="book-contents-title">Foundations</h2>
<div class="book-contents">…</div>

Contents as disclosures

A contents row navigates. When the row's content is short enough to live in place — a glossary definition, a spec, a one-paragraph answer — opening beats navigating, and the row becomes a <details> wearing the same class. The summary is the row; the panel is what opening reveals.

The title element depends on what the list is. A row title is a <span> when the list is navigation — a contents index the reader scans to choose a destination, where flooding the outline with row titles helps nobody. It becomes an <h3> when the list is the page's content: every row carries an anchor id, the rows are the thing the reader came for, and the page type emits no TOC. The glossary is the reference case. overrides margin, size, line-height and weight either way; the heading's slightly tighter letter-spacing is the one visible difference, and it matches the anchor rows, which have always used <h3>.

Panels with more than one paragraph may section themselves with <h4 class="label"> subtitles — real sub-sections only, in entries long enough to need them. The closing examples line is not one of those sections: it is a fixed field, so its label is a span inside the meta slot, not a heading.

<span class="book-contents-item-meta"><span class="label">Examples</span> the newsletter · a Reel</span>
Channel

A channel is any place where the brand meets an audience directly.

Examples the newsletter · the Instagram account · the website
Format

A format is the unit a channel accepts; regulations are scoped here.

Examples a Reel · a carousel · an issue · a deck
<div class="book-contents">
  <details class="book-contents-item" name="glossary-term" id="term-channel">
    <summary>
      <h3 class="book-contents-item-title">Channel</h3>
      <div class="book-contents-item-trailing">
        <div class="svg-icn" data-icon="chevron-down"><!-- inline svg --></div>
      </div>
    </summary>
    <div class="book-contents-item-panel">
      <p class="book-contents-item-description">A channel is any place where the brand meets an audience directly.</p>
      <span class="book-contents-item-meta"><span class="label">Examples</span> the newsletter · the Instagram account</span>
    </div>
  </details>
</div>

The code sample shows the reference-list form: <h3> title, anchor id, labelled examples. On a navigation list the title is a <span> and the id is optional — see the title rule above.

Single-open or multi-open. The same name value across a list gives native single-open — opening one row closes the rest, no JavaScript. Omit name and each row toggles independently. Older browsers without name support fall back to multi-open, which degrades gracefully. Never author open on more than one row in a name group — it is invalid, and browsers resolve it by closing all but one unpredictably.

The trailing icon is visible at rest, unlike the anchor row's hover-revealed arrow: a constant chevron says "opens in place" where the appearing arrow says "navigates". It rotates on open; under reduced motion the rotation is instant — the state change itself is the feedback.

No ARIA is needed. <details>/<summary> announce the disclosure role and the expanded state natively; adding aria-expanded double-announces in some screen readers. The chevron is decorative and already carries aria-hidden through the wrapper. Interactive content remains forbidden in every slot — the summary is already the control.


Accessibility

  • Use <a>, never a <div> with a click handler. Every cover and contents item that navigates is a link; the whole card is the link, so the accessible name is its full text content — title, description, and author together. A row that opens in place is a <details> instead — see "Contents as disclosures" — never a scripted div either way.
  • Keep meta short on link rows. Anything in or inside an <a> joins the link's accessible name. A date or a one-word status is fine; a sentence makes every card announce as a paragraph. The ceiling does not apply to a disclosure row — there the meta sits in the revealed panel and joins no accessible name.
  • The trailing icon is decorative and must carry aria-hidden="true". It repeats what the link already says. Icons rendered through the design system's wrapper already do this.
  • A leading icon needs a decision. If it duplicates the title, hide it with aria-hidden="true"; if it carries information the title does not, give it an accessible name.
  • Never nest interactive content in a slot. The card is already a link — a button or second link inside it is invalid HTML and unreachable in several screen-reader modes. A card needing a second action should be restructured so the action is a sibling of the link, not a child.
  • Heading levels are the caller's responsibility. Cards render <h3>. Place a group heading (<h2>) above the shelf or contents list so the page does not jump from <h1> to <h3>.
  • Focus and hover reveal the trailing icon equally. A keyboard user tabbing a shelf sees the same affordance a mouse user does. Both states also draw the focus ring, which is the real affordance — do not remove it.
  • Reduced motion is handled. --book-cover-lift is re-pointed to 0 under prefers-reduced-motion: reduce, so the hover lift stops while colour feedback stays.
  • Status must not be colour-only. A badge in a meta slot needs text or an icon alongside the colour.

Usage rules

Do:

  • Use + on overview and landing pages, wherever a visitor chooses a section to open
  • Use + one level down, listing the pages inside a section
  • Keep cover titles to one to four words so they do not wrap to a third line
  • Keep descriptions to one sentence; covers already set text-wrap: pretty, contents rows need data-text-wrap="pretty" explicitly
  • Re-point tokens to restyle; a product changes --book-cover-min-height, not the rule

Don't:

  • Don't add alongside — it is standalone and ships its own background, border, and hover states
  • Don't override min-height on a single cover; uniform height is what makes a shelf read as a shelf
  • Don't swap the trailing icon per card — constancy is what signals "this opens". That is By Default's usage rule, not a component constraint
  • Don't put extra content in ; the centred slot is title and description only
  • Don't mix and in one grid — pick one pattern per surface
  • Don't wrap a shelf in .grid.cols-2.gap-xl; the shelf owns its layout
  • Don't mix sizes in one shelf — uniform height is what makes an index read as a shelf
  • Don't give a contents row a header or footer strip; that turns a row into a card and blurs the shelf/contents distinction

CSS reference

This section documents how the component is built. For usage, see the sections above.

Tokens

Token Default What it controls
2 Column count on the shelf
Space between covers
250px Cover height — the shelf's visual rhythm
Cover title; data-size and the 960px step re-point it
Cover fill; dark mode re-points to
Cover fill on hover
var(--border-s) solid var(--border-faded) Cover border
var(--space-m) var(--space-l) Header and footer strip inset
Centred content inset
-2px Hover travel; re-pointed to 0 under reduced motion
0 7px 1px -3px var(--black-alpha-10) Hover shadow
var(--border-s) solid var(--border-faded) Contents list outer border
Row height
Space between a row's slots
Row title; steps down at 960px
Row fill on hover

Selectors

Selector Purpose
L0 grid of covers; owns columns and gap
.book-shelf[data-cols="3"] Three-column shelf
.book-cover[data-size="small|large"] Smaller or larger cover — height, title, and inset together
Group heading above a shelf
One closed book — a self-contained link card
/ Full-width strips, start slot and end slot
Header start icon; varies per card
Header end icon; hints at what clicking does
Centred title and description, absorbs remaining height
/ Cover text
Footer start — "by X"
Footer end — metadata: a tag, a date, a count
L1 table of contents; draws the outer border
Group heading above a contents list
One line in the contents
details.book-contents-item The same row as a disclosure — opens in place instead of navigating
details.book-contents-item > summary The visible row line; carries the row layout, hover, and focus ring
Revealed content: description and meta
Leading icon; varies per row
Title and description column; grows to fill
/ Row text
Metadata, inside the body under the description
Trailing icon; hints at what clicking does
"by X", inside meta

Use in another product

The design system installs once per product:

npm install github:bydefaultstudio/design-system-dist#semver:^4.7.0

This component's styles ship in design-system.css. No JavaScript, nothing else to include.

On this page
  • Anatomy
  • Basic usage
  • Using the header slots
  • Using the footer slots
  • Sizes
  • Contents
  • Using the row slots
  • Columns
  • Group headings
  • Contents as disclosures
  • Accessibility
  • Usage rules
  • CSS reference
  • Use in another product
Previous Table
Next Page Navigation

Was this page helpful?

We use this feedback to improve our documentation.

Thanks for your feedback

Send feedback

© 2026 By Default