Typography

The semantic content engine.

Typography in Keystone is not merely aesthetic; it is strict architecture. The .ks-prose utility acts as a container for user-generated content (Markdown), enforcing Vertical Rhythm and WCAG 2.2 spacing rules automatically.

It isolates your blog posts and documentation from the rest of the layout, preventing CSS conflicts while ensuring optimal readability.

The Architecture of Text

Typography defines the structural hierarchy of information. Keystone uses Contextual Spacing to manage flow.

Vertical Rhythm

Notice how the space after the paragraphs depends on the following element.

Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptate et accusamus nemo expedita voluptates a asperiores sunt?

Laudantium provident velit, dolorum iure maiores, esse ipsa harum sequi minima quisquam sed totam excepturi placeat sapiente enim labore officia ea! Culpa vitae quaerat molestiae numquam fuga aspernatur sunt architecto nostrum.

  • Lists use specific indentation.
  • Nested lists change markers automatically.
    1. Ordered nested
      • Or unordered sub-nested list

We also handle inline code and blockcodes:

{{- /* I'am a comment */ -}}
{{- /*I' am a long comment that I will exceed the layout boundaried and overflow the container*/-}}

And of course tables

VariableValue
Spacing2.25em
Width80ch

HTML
1{{- with .Content }}
2  <article class="bg-background text-foreground">
3    <h1 class="ks-prose">The Architecture of Text</h1>
4    <!-- Generated via Markdown -->
5    <div class="ks-prose">{{ .Content }}</div>
6  </article>
7{{- end }}

Reference

The system relies on three architectural pillars:

  1. Namespacing: All rules are scoped to .ks-prose. It does not pollute global generic tags like p or h1.
  2. Contextual Selectors: It uses :has() to change margins dynamically. A paragraph followed by a Heading (h2) gets more breathing room than a paragraph followed by a List (ul).
  3. Mathematical Harmony: Spacing follows the formula: Line-Height (1.5) * WCAG Multiplier (1.5) = 2.25em.

Installation

The typography system is disabled by default to prevent override issues.
Uncomment the import in your main CSS file:

assets/css/main
1- /* @import './components/_typography.css'; */
2+ @import './components/_typography.css';

Define your rulesets in the assets/css/components/_typography.css.

Prerequisites
The Get Started: Manual Installation steps must be completed!

Create the file:

Tailwind CSS assets/components/_typography.css
  1@layer components {
  2  .ks-prose {
  3    /* Container */
  4    @apply w-full max-w-[80ch] px-2;
  5    @apply leading-normal antialiased;
  6
  7    /* Reset to prevent container gaps */
  8    & > :first-child {
  9      @apply mt-0;
 10    }
 11    & > :last-child {
 12      @apply mb-0;
 13    }
 14
 15    /* Vertical rythm defined by <p>*/
 16    p {
 17      /* 1.5 (line-height) * 1.5 (WCAG requirement) = 2.25em */
 18      @apply mb-[2.25em];
 19      @apply [&_:has(+_:not(p))]:mb-[1.5em];
 20    }
 21    p:has(+ h2) {
 22      @apply mb-[3em];
 23    }
 24    p:has(+ h3) {
 25      @apply mb-[2em];
 26    }
 27    p:has(+ :is(ul, ol, pre)) {
 28      @apply mb-[0.75em];
 29    }
 30
 31    /* Headings */
 32    h1,
 33    h2,
 34    h3,
 35    h4,
 36    h5,
 37    h6 {
 38      @apply mb-[0.6em];
 39      @apply scroll-m-20;
 40      @apply leading-relaxed tracking-tight text-balance;
 41    }
 42    h1 {
 43      @apply text-[2.25em] font-bold;
 44    }
 45    h2 {
 46      @apply text-[1.875em] font-semibold;
 47    }
 48    h3 {
 49      @apply text-[1.5em] font-medium;
 50    }
 51    h4 {
 52      @apply text-[1.25em];
 53    }
 54    h5 {
 55      @apply text-[1.125em] tracking-wide uppercase;
 56    }
 57    h6 {
 58      @apply text-[1em] tracking-wide uppercase;
 59    }
 60
 61    /* Multimedia */
 62    :is(img, picture, video, figure) {
 63      @apply mt-[2em] mb-[2em];
 64    }
 65    /** Reset margins for nested media **/
 66    :is(picture, figure) > img {
 67      @apply my-0;
 68    }
 69
 70    /* Links (button component exclusion) */
 71    a:not(a.btn) {
 72      @apply font-medium underline decoration-2 underline-offset-2;
 73      @apply hover:decoration-3;
 74      @apply visited:font-normal;
 75      @apply active:no-underline;
 76      @apply transition-all duration-300;
 77    }
 78    /** Styles for `_markup/render-link.html` **/
 79    .ks-external-link {
 80      @apply inline-flex items-center;
 81    }
 82
 83    /* Lists */
 84    /** Deep indent for readability **/
 85    ul,
 86    ol {
 87      @apply mb-[1.5em] pl-[2.25em];
 88    }
 89
 90    ul {
 91      @apply list-disc;
 92    }
 93
 94    ol {
 95      @apply list-decimal;
 96    }
 97
 98    li {
 99      @apply pl-[0.25em];
100      /** Nested lists **/
101      & > :is(ul, ol) {
102        @apply mt-0 mb-[0.5em] pl-[1em];
103      }
104      & > :is(ul) {
105        @apply list-[circle];
106      }
107      & > :is(ol) {
108        @apply list-[lower-roman];
109      }
110    }
111
112    /* Blockquotes */
113    blockquote {
114      @apply my-[2em] py-1.5 pl-6;
115      @apply bg-muted/50 rounded-sm;
116      @apply text-muted-foreground italic;
117      @apply border-primary border-x-4;
118
119      & > p {
120        @apply mb-0;
121      }
122    }
123
124    /* Tables */
125    table {
126      @apply my-[2em] w-full overflow-y-auto;
127      @apply text-left text-sm;
128      @apply border-collapse border;
129    }
130
131    thead {
132      @apply bg-muted text-muted-foreground;
133    }
134
135    th {
136      @apply border-b px-4 py-2 font-medium;
137    }
138
139    td {
140      @apply border-b px-4 py-2;
141    }
142
143    tr {
144      @apply last:border-b;
145    }
146
147    tr:hover {
148      @apply bg-muted/50;
149    }
150
151    /* Code */
152    /** Global reset for code for crisp rendering **/
153    code {
154      @apply font-mono subpixel-antialiased;
155    }
156    /** Inline code **/
157    :not(pre) > code {
158      @apply relative;
159      @apply px-[0.3rem] py-[0.2rem];
160      @apply bg-muted text-muted-foreground text-sm font-medium;
161      @apply rounded-sm;
162    }
163
164    /** Block code **/
165    pre {
166      @apply mb-[1.5em];
167      @apply bg-muted text-muted-foreground text-sm;
168      @apply rounded-md border;
169      @apply overflow-x-hidden;
170    }
171    pre code {
172      @apply block;
173      @apply px-4 py-2;
174      @apply overflow-x-auto;
175    }
176
177    /** Misc Inline **/
178    hr {
179      @apply my-[2.25em] border;
180    }
181  }
182}

Import it in your main Tailwind file:

Tailwind CSS assets/main.css
1/* Components */
2/** (Import Keystone components CSS here) */
3@import './components/_typography.css';

Usage

Wrap the Markdown output variable in the .ks-prose class.

HTML
1<div class="ks-prose">
2  {{ .Content }}
3</div>

Accessibility

The system is engineered to meet WCAG 2.2 AA standards:

  • Visual Presentation (SC 1.4.8): Paragraph spacing is strictly calculated to be 1.5 times the line spacing, aiding users with cognitive disabilities in tracking lines.
  • Text Spacing (SC 1.4.12): Line height (leading-normal) and paragraph spacing use relative units (em) to scale correctly if the user overrides the root font size.
  • Readability: The max-w-[80ch] of the container restricts line length to the maximum recommended ~80 characters. Lines longer than this cause eye fatigue and tracking errors for users with reading difficulties.
  • Contrast: Use of themes variables to ensure WCAG 2.2 AAA contrast and better legibility out of the box.