Component reference · Editorial style guide

Component reference

A living reference of every visual pattern on this site. All examples below are rendered live — if something looks wrong here, it looks wrong everywhere.

Colors#

Design tokens defined as CSS custom properties on the body. The site uses a warm paper background (#fbf6ef) with dark text (#222) and blue accents.

Page background
#fbf6ef
Body text
#222
Grey darkest
--kh-color--grey--darkest
Grey
--kh-color--grey
Blue (links)
--kh-color--blue
Blue dark
--kh-color--blue--dark
Visited links
#734595
Orange (accent)
--kh-color--orange
Yellow dark
#ffb81c
Surface (boxes, tables)
#fafafa
Table header
#d0d0ce
Code/quote bg
#fff

Typography#

All type is set in Recursive, a variable font with axes for weight, monospace, casual, slant, and cursive. Base size is 16px with 1.7 line height.

Heading classes#


<h1 class="kh-text-heading--1">Heading 1 — 42px / 700 weight</h1>
<h2 class="kh-text-heading--2">Heading 2 — 30px / 500 weight</h2>
<h3 class="kh-text-heading--3">Heading 3 — 27px / 500 weight</h3>

Heading 1 — 42px / 700 weight

Heading 2 — 30px / 500 weight

Heading 3 — 27px / 500 weight

Content headings (unstyled h-tags inside .kh-content)#

Inside .kh-content, bare heading tags inherit proportional sizing:


<div class="kh-content">
  <h1>Content H1 — 42px</h1>
  <h2>Content H2 — 30px</h2>
  <h3>Content H3 — 27px</h3>
  <h4>Content H4 — 21px</h4>
  <h5>Content H5 — 19px</h5>
</div>

Content H1 — 42px

Content H2 — 30px

Content H3 — 27px

Content H4 — 21px

Content H5 — 19px

Body text classes#


<p class="kh-text-body--1">Body 1 — 32px, weight 500. Used for post teasers and lead text.</p>
<p class="kh-text-body--3">Body 3 — 16px, weight 500. Used for metadata and supporting text in dark grey (#373a36).</p>

Body 1 — 32px, weight 500. Used for post teasers and lead text.

Body 3 — 16px, weight 500. Used for metadata and supporting text in dark grey (#373a36).

Inline text styles#


<p>Regular paragraph text at 16px with 1.7 line height.</p>
<p><em>Italic text uses font-variation-settings for a slanted casual style.</em></p>
<p><strong>Bold text for emphasis.</strong></p>
<p><small>Small text — 13px, used for dates and fine print.</small></p>
<p>Text with <code>inline code</code> rendered in monospace Recursive.</p>
<p>An <abbr title="Abbreviation with tooltip">ABBR</abbr> gets a dotted underline and help cursor.</p>

Regular paragraph text at 16px with 1.7 line height.

Italic text uses font-variation-settings for a slanted casual style.

Bold text for emphasis.

Small text — 13px, used for dates and fine print.

Text with inline code rendered in monospace Recursive.

An ABBR gets a dotted underline and help cursor.

Buttons#

The button has a distinctive 3D shadow effect that animates on hover (shifts 4px) and active (shifts 8px, shadow collapses).


<a href="#buttons" class="kh-button">Default button</a>
<a href="#buttons" class="kh-button kh-button--sm">Small button</a>

The shadow uses --kh-button-shadow-background-color (defaults to blue-dark). Border color is --kh-button-border-color (defaults to blue). These can be overridden via CSS custom properties.

Badges and labels#


<span class="kh-badge">Badge</span>
<span class="kh-badge">UPPERCASE</span>
Badge UPPERCASE

Labels#

Pill-shaped categorization chips used for topics and tags:


<span class="kh-label">web development</span>
<span class="kh-label">design systems</span>
<span class="kh-label">Eleventy</span>
<span class="kh-label">performance</span>
web development design systems Eleventy performance

Note boxes#

Orange-bordered callout boxes for asides, updates, and contextual information. The text uses a casual variant of Recursive with a slight slant.


<aside class="kh-note-box">
  <span class="kh-note-box__label">Update</span>
  This pattern was revised in September 2025 to use the casual axis of Recursive for a more handwritten feel.
</aside>

<aside class="kh-note-box">
  <span class="kh-note-box__label">Context</span>
  The label uses a bold monospace casual variant. Keep labels short: "Update", "Sidebar", "Related", "Context".
</aside>

Content boxes#

General-purpose container with a bottom border accent. Themeable via --kh-box-theme-color--background and --kh-box-theme-color--foreground.


<div class="kh-box">
  <p>A content box with default styling — light background, 4px solid bottom border, and 1rem padding.</p>
  <p>Links inside <code>.kh-box__text</code> inherit the current color.</p>
</div>

A content box with default styling — light background, 4px solid bottom border, and 1rem padding.

Links inside .kh-box__text inherit the current color.

Collapsible details#

A styled <details> element for secondary content that readers can expand on demand. Uses the site's blue border and offset shadow to match the button aesthetic. The heading inside <summary> renders inline next to the disclosure triangle.


<details class="kh-details">
  <summary><h3>Section title goes here</h3></summary>
  <p>This content is hidden by default and revealed when the reader clicks the summary. Use it for supplementary information, prior art, alternative approaches, or anything that would interrupt the main flow.</p>
  <p>Any HTML works inside — lists, code blocks, images.</p>
</details>

Section title goes here

This content is hidden by default and revealed when the reader clicks the summary. Use it for supplementary information, prior art, alternative approaches, or anything that would interrupt the main flow.

Any HTML works inside — lists, code blocks, images.

Open by default#

Add the open attribute to show the content expanded on page load:


<details class="kh-details" open>
  <summary><h3>Expanded by default</h3></summary>
  <p>The open state adds a bottom border below the summary to separate it from the content. The disclosure triangle rotates to indicate the expanded state.</p>
</details>

Expanded by default

The open state adds a bottom border below the summary to separate it from the content. The disclosure triangle rotates to indicate the expanded state.

When to use#

  • Lengthy prior art or background that most readers can skip
  • Alternative approaches considered but not chosen
  • Technical implementation details on a non-technical post
  • Code examples that supplement but aren't essential to the narrative

Markup notes#

  • The heading level inside <summary> should match your document outline (use <h2>, <h3>, etc. as appropriate)
  • The .kh-details class handles all styling — no additional classes needed on child elements
  • Works with {% markdown %} blocks inside for rich content

Blockquotes#

Standard blockquote#

Inside .kh-content, bare blockquotes get a white background, subtle shadow, rounded corners, and a left border accent:


<div class="kh-content">
  <blockquote>
    <p>The best way to predict the future is to invent it. Well-structured content is the foundation of every good digital experience.</p>
  </blockquote>
</div>

The best way to predict the future is to invent it. Well-structured content is the foundation of every good digital experience.

A styled blockquote with a yellow left border, opening quote mark, and a hand-drawn highlighter effect behind the text:


<blockquote class="kh-quote--featured">
  <p>The work you're most proud of is rarely the flashiest — it's the system that quietly keeps working long after you've moved on.</p>
</blockquote>

Code#

Inline code#

Inline code uses monospace Recursive with a white background and slight padding.

Code blocks#

Fenced code blocks inside .kh-content get a white background, rounded corners, and a subtle shadow:

.kh-button {
  appearance: none;
  background-color: #fafafa;
  border: 4px solid var(--kh-color--blue);
  border-radius: 4px;
  box-shadow: 8px 8px 0 var(--kh-color--blue--dark);
  transition: all linear 125ms;
}
<aside class="kh-note-box">
  <span class="kh-note-box__label">Note</span>
  Content goes here.
</aside>

Code blocks use pre-wrap for wrapping and overflow: auto for horizontal scroll when needed.

Figures and images#

Styled figure#


<figure class="kh-figure">
  <img class="kh-figure__image" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='300' fill='%23d0d0ce'%3E%3Crect width='600' height='300'/%3E%3Ctext x='50%25' y='50%25' dominant-baseline='middle' text-anchor='middle' fill='%23707372' font-family='system-ui' font-size='18'%3E600 × 300 placeholder%3C/text%3E%3C/svg%3E" alt="Placeholder image demonstrating the figure component">
  <figcaption class="kh-figure__caption">Caption text — used for attribution and context. Grey (#373a36), weight 500.</figcaption>
</figure>
Placeholder image demonstrating the figure component
Caption text — used for attribution and context. Grey (#373a36), weight 500.

Unstyled figure (inside .kh-content)#

Bare figure tags inside .kh-content use display: table with bottom-aligned captions:


<div class="kh-content">
  <figure>
    <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='200' fill='%23d0d0ce'%3E%3Crect width='400' height='200'/%3E%3Ctext x='50%25' y='50%25' dominant-baseline='middle' text-anchor='middle' fill='%23707372' font-family='system-ui' font-size='16'%3E400 × 200%3C/text%3E%3C/svg%3E" alt="Placeholder image">
    <figcaption>A plain figure caption in grey (#54585a).</figcaption>
  </figure>
</div>
Placeholder image
A plain figure caption in grey (#54585a).

Bleed-out image#

The .kh-bleed-out utility makes images break out of their container on larger screens (30% on medium, 50% on large):


<img class="kh-bleed-out" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='900' height='300' fill='%23d0d0ce'%3E%3Crect width='900' height='300'/%3E%3Ctext x='50%25' y='50%25' dominant-baseline='middle' text-anchor='middle' fill='%23707372' font-family='system-ui' font-size='18'%3EBleed-out image (wider on large screens)%3C/text%3E%3C/svg%3E" alt="Demonstration of bleed-out image effect">
Demonstration of bleed-out image effect

Tables#

Inside .kh-content, bare tables get alternating row striping with a grey header row:


<div class="kh-content">
  <table>
    <thead>
      <tr>
        <th scope="col">Property</th>
        <th scope="col">Value</th>
        <th scope="col">Notes</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>Background</td>
        <td><code>#fafafa</code></td>
        <td>Light surface color</td>
      </tr>
      <tr>
        <td>Header bg</td>
        <td><code>#d0d0ce</code></td>
        <td>Warm grey</td>
      </tr>
      <tr>
        <td>Even rows</td>
        <td><code>#d8d8d8</code></td>
        <td>Alternating stripe</td>
      </tr>
      <tr>
        <td>Cell padding</td>
        <td><code>8px 16px</code></td>
        <td>Comfortable reading</td>
      </tr>
    </tbody>
  </table>
</div>
Property Value Notes
Background #fafafa Light surface color
Header bg #d0d0ce Warm grey
Even rows #d8d8d8 Alternating stripe
Cell padding 8px 16px Comfortable reading

Forms#

Form elements used for the site search. The search input has a custom SVG cancel button on WebKit browsers.


<div class="kh-form__item">
  <label class="kh-form__label" for="demo-search">Search</label>
  <input type="search" id="demo-search" placeholder="Search my site" class="kh-form__input">
</div>
<a href="#forms" class="kh-button">Search</a>
Search

Search mode toggle#

A tabbed radio control used on the search page to switch between keyword and semantic search. On the search page, the tabs sit above a .kh-search-widget__panel card — the active tab merges into the panel via a matching background and erased bottom border. Hidden radio inputs keep it accessible — keyboard users can arrow between options and screen readers announce the selected state.


<div class="kh-search-widget">
  <fieldset class="kh-search-mode">
    <legend class="kh-u-sr-only">Search mode</legend>
    <label class="kh-search-mode__option">
      <input type="radio" name="demo_search_mode" value="keyword" checked>
      Search by keyword
    </label>
    <label class="kh-search-mode__option">
      <input type="radio" name="demo_search_mode" value="semantic">
      Search semantically
    </label>
  </fieldset>
  <div class="kh-search-widget__panel">
    <p style="margin:0;color:var(--kh-color--grey)">Panel content goes here</p>
  </div>
</div>
Search mode

Panel content goes here

Markup notes#

  • The <fieldset> starts with hidden in production — JavaScript removes it on load (progressive enhancement, since the toggle only works with JS)
  • Radio name must be unique per instance to avoid conflicts
  • The active state uses :has(input:checked) to style the parent label — no JS needed for the visual toggle
  • :has(input:focus-visible) provides an orange outline for keyboard focus
  • Wrap in .kh-search-widget with a .kh-search-widget__panel to get the connected tabs-to-card effect

Search form#

.kh-search-widget__form is a shared flex layout used by both the keyword and semantic search inputs. The button includes two inner spans — .kh-button__label (text) and .kh-button__icon (unicode arrow) — that swap at the mobile breakpoint.

  • Desktop: full text button ("Search" / "Ask") with the standard border + shadow style
  • Mobile (<600px): text hidden, button becomes a small blue circle with a arrow, positioned inside the input field

<div class="kh-search-widget">
  <div class="kh-search-widget__panel" style="border-radius:4px">
    <form class="kh-search-widget__form" onsubmit="return false">
      <label class="kh-u-sr-only" for="demo-search">Search</label>
      <input type="search" id="demo-search" placeholder="Search my site" class="kh-form__input">
      <button type="submit" class="kh-button" aria-label="Search">
        <span class="kh-button__label" aria-hidden="true">Search</span>
        <span class="kh-button__icon" aria-hidden="true">&#x2192;</span>
      </button>
    </form>
  </div>
</div>

Markup notes#

  • Always include aria-label on the button so the purpose is announced when only the icon is visible
  • .kh-button__label and .kh-button__icon both carry aria-hidden="true" — the aria-label is the accessible name
  • Resize to a narrow viewport to see the icon-inside-input behaviour

Summary cards#

Used on the blog index and work pages to display post previews.

Standard summary#


<article class="kh-summary">
  <div class="kh-summary__date">7 Feb 2026</div>
  <h3 class="kh-summary__title">
    <a href="#summary-cards" class="kh-summary__link">Example post title with balanced text wrapping</a>
  </h3>
  <p class="kh-summary__text">A one-sentence teaser that summarizes the post content and entices the reader to click through.</p>
</article>

News summary#

A more compact variant with date on its own row and two-column layout on wider screens:


<article class="kh-summary kh-summary--news">
  <div class="kh-summary__date">7 Feb 2026</div>
  <h3 class="kh-summary__title">
    <a href="#summary-cards" class="kh-summary__link">News-style summary with tighter spacing</a>
  </h3>
  <p class="kh-summary__text">Used on the blog index. Grid layout stacks on mobile, goes side-by-side on screens wider than 600px.</p>
</article>

Summary with image#

Summaries can include a thumbnail image that snaps to the right column on medium+ screens:


<article class="kh-summary kh-summary--news">
  <div class="kh-summary__date">7 Feb 2026<span class="kh-summary__author">Filed in: design systems, performance</span></div>
  <h3 class="kh-summary__title">
    <a href="#summary-cards" class="kh-summary__link">Post with a thumbnail image</a>
  </h3>
  <a href="#summary-cards" class="kh-summary__image">
    <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='147' fill='%23d0d0ce'%3E%3Crect width='220' height='147'/%3E%3Ctext x='50%25' y='50%25' dominant-baseline='middle' text-anchor='middle' fill='%23707372' font-family='system-ui' font-size='14'%3E220×147%3C/text%3E%3C/svg%3E" alt="Example thumbnail">
  </a>
  <p class="kh-summary__text">The image uses a 3:2 aspect ratio with object-fit cover. Constrained to 220px wide.</p>
</article>
7 Feb 2026Filed in: design systems, performance

Post with a thumbnail image

Example thumbnail

The image uses a 3:2 aspect ratio with object-fit cover. Constrained to 220px wide.

Metrics#

Used on the CV page to display key impact numbers. Cards have a subtle hover lift effect.


<div class="kh-metrics-grid">
  <div class="kh-metric">
    <span class="kh-metric__number">20+</span>
    <span class="kh-metric__label">YEARS EXPERIENCE</span>
  </div>
  <div class="kh-metric">
    <span class="kh-metric__number">0.45s</span>
    <span class="kh-metric__label">PAGE LOAD TIME</span>
  </div>
  <div class="kh-metric">
    <span class="kh-metric__number">3 weeks</span>
    <span class="kh-metric__label">EDITORIAL TIME SAVED / MONTH</span>
  </div>
</div>
20+ YEARS EXPERIENCE
0.45s PAGE LOAD TIME
3 weeks EDITORIAL TIME SAVED / MONTH

Snapshot list#

A timeline-style list for career milestones, with bold labels and bottom borders:


<div class="kh-career-snapshot">
  <ul class="kh-snapshot-list">
    <li><strong>2023 – present</strong> Platform Architect at UNDRR</li>
    <li><strong>2018 – 2023</strong> Web Design Architect at EMBL</li>
    <li><strong>2015 – 2018</strong> Lead Developer at EMBL-EBI</li>
  </ul>
</div>
  • 2023 – present Platform Architect at UNDRR
  • 2018 – 2023 Web Design Architect at EMBL
  • 2015 – 2018 Lead Developer at EMBL-EBI

Layout#

Breakpoints#

Token Value Usage
$kh-breakpoint--sm 600px Small screens up
$kh-breakpoint--md 846px Medium screens up
$kh-breakpoint--lg 1024px Large screens up
$kh-breakpoint--xl 1300px Extra large
$kh-breakpoint--sm-down 599px Below small
$kh-breakpoint--md-down 845px Below medium
$kh-breakpoint--lg-down 1023px Below large

Grid system (.kh-grid)#

The base grid uses CSS Grid with auto-flow columns. Children reset their margin and max-width.


<div class="kh-grid" style="--kh-page-grid-gap: 1rem;">
  <div style="background: #d0d0ce; padding: 1rem; border-radius: 4px;">Column 1</div>
  <div style="background: #d0d0ce; padding: 1rem; border-radius: 4px;">Column 2</div>
  <div style="background: #d0d0ce; padding: 1rem; border-radius: 4px;">Column 3</div>
</div>
Column 1
Column 2
Column 3

Three-column grid (.kh-grid__col-3)#

Explicit three-column layout. Collapses to single column below 846px. Use .kh-grid__col--span-1 and .kh-grid__col--span-2 for asymmetric layouts:


<div class="kh-grid kh-grid__col-3" style="--kh-page-grid-gap: 1rem;">
  <div class="kh-grid__col--span-1" style="background: #d0d0ce; padding: 1rem; border-radius: 4px;">Span 1</div>
  <div class="kh-grid__col--span-2" style="background: #ffb81c; padding: 1rem; border-radius: 4px;">Span 2 (wider)</div>
</div>
Span 1
Span 2 (wider)

Stylized grid (.kh-grid-stylized)#

The signature layout pattern: a left gutter (16rem on desktop) with main content to the right. Used for most page sections. This page uses it for every section.


<div class="kh-grid-stylized" style="border: 2px dashed #d0d0ce; padding: 1rem;">
  <div style="background: #d0d0ce; padding: 1rem; border-radius: 4px; text-align: center;">← Gutter (16rem)</div>
  <div style="background: #ffb81c; padding: 1rem; border-radius: 4px;">Main content area</div>
</div>
← Gutter (16rem)
Main content area

Stack (.kh-stack)#

Vertical rhythm utility. Adds 1rem margin between sibling children. Use .kh-stack--800 for 2rem spacing:


<div class="kh-stack" style="border: 2px dashed #d0d0ce; padding: 1rem;">
  <div style="background: #d0d0ce; padding: 1rem; border-radius: 4px;">Item 1</div>
  <div style="background: #d0d0ce; padding: 1rem; border-radius: 4px;">Item 2 (1rem gap)</div>
  <div style="background: #d0d0ce; padding: 1rem; border-radius: 4px;">Item 3 (1rem gap)</div>
</div>
Item 1
Item 2 (1rem gap)
Item 3 (1rem gap)

Cluster (.kh-cluster)#

Horizontal flex layout with wrapping and center alignment. Used for navigation and grouped elements:


<div class="kh-cluster">
  <span class="kh-label">Item 1</span>
  <span class="kh-label">Item 2</span>
  <span class="kh-label">Item 3</span>
  <span class="kh-label">Item 4</span>
  <span class="kh-label">Item 5</span>
</div>
Item 1 Item 2 Item 3 Item 4 Item 5

Full bleed (.kh-u-fullbleed)#

Makes a section's background extend to the full viewport width while keeping content constrained. Used for the post title bar and footer:


<div class="kh-u-fullbleed kh-u-background-color--yellow--dark" style="padding: 2rem 0;">
  <p style="max-width: 70ch;"><strong>Full bleed section.</strong> The yellow background extends edge-to-edge while the text stays within the content width.</p>
</div>

Full bleed section. The yellow background extends edge-to-edge while the text stays within the content width.

Spacing utilities#

Utility classes for margin and padding. The naming follows a sizing scale.

Class Property Value
.kh-u-margin__bottom--200 margin-bottom 0.5rem
.kh-u-margin__bottom--600 margin-bottom 1.5rem
.kh-u-margin__top--1200 margin-top 3rem
.kh-u-padding__bottom--200 padding-bottom 0.75rem
.kh-u-padding__bottom--400 padding-bottom 1rem
.kh-u-padding__top--500 padding-top 1.5rem
.kh-u-padding__top--600 padding-top 2rem
.kh-u-padding__top--800 padding-top 3rem

Content width#

The .kh-content class constrains content to max-width: 70ch for readable line lengths. Add .kh-content--wide to remove the constraint.

Font variations#

Recursive exposes five variable axes. These helper classes apply preset combinations:


<p class="kh-font-headline" style="font-size: 32px;">Headline style — weight 800, casual 1, mono 0</p>
<p class="kh-font-code">Code style — mono 1, casual 0, no slant, no cursive</p>
<p class="kh-logo" style="font-size: 20px;">Logo style — weight 800, mono 1, casual 0</p>

Headline style — weight 800, casual 1, mono 0

Code style — mono 1, casual 0, no slant, no cursive

Display headlines#

Extra-large headings using .kh-font-headline--display. When two display headlines are adjacent, the first becomes 80px with a light weight:


<h2 class="kh-font-headline kh-font-headline--display">Display headline — 60px</h2>

Display headline — 60px

Variable font axes reference#

Axis Code Range Default Usage
Weight wght 300–800 400 Light to extra-bold
Monospace MONO 0–1 0 Proportional to monospace
Casual CASL 0–1 0 Formal to hand-drawn
Slant slnt -15–0 0 Upright to italic
Cursive CRSV 0–1 0.5 Controls cursive letterforms

Video embed#

The .kh-video wrapper provides a responsive 16:9 container. Iframes, objects, and embeds are positioned absolutely to fill the container:


<div class="kh-video">
  <div style="position:absolute;top:0;left:0;width:100%;height:100%;background:#d0d0ce;display:flex;align-items:center;justify-content:center;color:#707372;font-size:18px;">16:9 video placeholder</div>
</div>
16:9 video placeholder

Feedback toast#

A fixed bottom banner triggered by CSS :target. Used after the feedback Worker redirects back with a #thanks fragment. No JavaScript required.

How it works#

  1. The element is hidden by default (display: none)
  2. When #thanks appears in the URL, :target activates the banner
  3. Banner slides up from the bottom with a spring easing (0.6s)
  4. A blue timer bar along the top border shrinks from 100% to 0 over 15 seconds
  5. After 15 seconds the banner slides back down and hides

Markup#

The element needs an id that matches the URL fragment, the .kh-feedback-thanks class, and aria-live="polite" so screen readers announce the message.

<span id="thanks" class="kh-feedback-thanks" aria-live="polite">
  Thanks for the feedback!
</span>

The feedback link redirects back to the page with the fragment:

<a href="https://feedback.example.com/up/posts/my-post/"
   rel="nofollow">👍 This was useful</a>

The Worker returns 302 Location: https://example.com/posts/my-post/#thanks, which activates :target on the banner element.

Design details#

  • Surface: #fafafa background, blue text — matches button palette
  • 3D shadow: box-shadow above the banner uses --kh-color--blue--dark
  • Timer bar: ::after pseudo-element, 4px blue line, shrinks linearly over 15s
  • Emoji: ::before adds a 🎉 prefix
  • Auto-dismiss: kh-banner-out animation fires at 15s, slides the banner down and sets visibility: hidden
  • Reusable: works anywhere — the position: fixed breaks out of any container context

Demo

Click the link below to trigger the toast. It appends #feedback-toast-demo to the URL, activating the :target state on the demo element.

Trigger toast demo

Thanks for the feedback!

Accessibility patterns#

A hidden link that becomes visible on focus, allowing keyboard users to skip to main content:

<a href="#main-content" class="kh-u-sr-only kh-skip-link">Skip to main content</a>

Tab to the top of this page to see it in action.

Screen reader only#

The .kh-u-sr-only class visually hides content while keeping it accessible to screen readers:

<h2 class="kh-u-sr-only">Section title visible only to screen readers</h2>

Focus indicators#

All interactive elements get a 1px dashed outline with 4px offset on focus. This applies to:

  • All links inside .kh-content
  • All elements with a kh-* class

CSS toggle#

The site's CSS can be toggled off via a checkbox in the footer (#kh-css-toggle). All custom styles are scoped under body:has(#kh-css-toggle:checked), so unchecking it reveals the bare HTML structure — useful for testing structural accessibility.

Reduced motion#

Animations (the dappled-light ambience effect and venetian blind sway) are gated behind @media (prefers-reduced-motion: no-preference) and disabled by default.

High contrast#

The ambient background effects are disabled entirely under @media (prefers-contrast: high).

Utility classes#

Visibility#

Class Behavior
.kh-u-show-for-mobile-only Visible below 600px, hidden above
.kh-u-show-for-medium-up Visible 846px and up
.kh-u-show-for-medium-only Visible 846px – 1023px only
.kh-u-show-for-large Visible 1024px and up
.kh-u-show-for-print-only Hidden on screen, visible when printed
.kh-u-do-not-print Visible on screen, hidden when printed
.kh-u-sr-only Visually hidden, accessible to screen readers

Text#

Class Effect
.kh-u-text--nowrap Prevents text from wrapping
.kh-u-text-color--grey Sets text color to #707372

Color#

Class Effect
.kh-u-background-color--yellow--dark Background #ffb81c

The .kh-link class provides blue link styling with purple visited state, independent of .kh-content context:

<a href="/example" class="kh-link">Styled link</a>
Class Purpose
.kh-navigation Navigation wrapper, pushed to right with margin-left: auto
.kh-navigation__list Flex list with gap spacing, no bullets
.kh-navigation__item Inline-flex list item
.kh-navigation__link Link with no underline, underlines on hover
.kh-navigation__link--active Bold weight for current page

Print#

Links in .kh-content lose their decoration on print. Elements with .kh-u-do-not-print are hidden. The .ken-link--print class shows the href URL below the link text.


CSS custom properties reference#

All custom properties are scoped to body:has(#kh-css-toggle:checked):

Colors#

Property Default
--kh-color--grey #707372
--kh-color--grey--darkest #373a36
--kh-color--blue #3b6fb6
--kh-color--blue--dark #193f90
--kh-color--orange #f49e17

Layout#

Property Default Notes
--kh-body-width 81.25em Max page width
--kh-page-grid-gap 1rem / 2rem Grid gap (responsive)
--kh-grid-stylized-module--prime 16rem Left gutter width

Spacing#

Property Default Notes
--kh-stack-margin 1rem Vertical stack gap
--kh-text-margin--bottom 16px Text element bottom margin
--kh-box-padding 1rem Box component padding
--kh-cluster-margin 0.5rem Cluster item spacing

Theming#

Property Purpose
--kh-box-theme-color--background Box background override
--kh-box-theme-color--foreground Box text color override
--kh-button-background-color Button background
--kh-button-border-color Button border
--kh-button-shadow-background-color Button shadow

Editorial style guide Colophon