Baselayer 3: Typography
Font stacks
A few native font stacks are set in the root-vars.css file.
:root {
--t-sans: ui-sans-serif, sans-serif;
--t-serif: ui-serif, serif;
--t-mono: ui-monospace, monospace;
--t-base-ff: var(--t-sans);
--t-prose: var(--t-serif);
}
Everything here is an example, a place to start. Your own --base and --prose don’t need to be both sans-serif and serif. See setting font stacks for more information.
The Baselayer docs make use of JavaScript and some extra CSS to enable a demo toggle, so that you can switch between --base-ff and --prose in this main content area — see main menu.
Font stack usage in Baselayer:
- The base font stack
--t-base-ffis set in the<body>tag and via thet-baseutility class. It is also set on form inputs and on heading tags (using--h-ff: inherit;) so that you can override and set a different heading typeface). - The prose is only available via the
t-proseutility class. Employ it on your<article>tag, or wherever you need. - The monotype is available via the
<code>,<kbd>, and<samp>HTML tags, and in thet-monoutility class.
Setting font stacks
In choosing your own font stacks, you will probably need two fonts that have similar x-height CSS property, so that you can set them using the same font size and line-height. Some other factors to compare are hights for lowercase ascenders and descenders, letter width, and stroke thickness.
If you want to stick with using native font stacks (i.e. those that come with computers, tablets, and phones), then the Modern Font Stacks website has several examples that you can easily copy.
In choosing your own typefaces from elsewhere, a good place to start is by pairing serif and sans-serif (or slab serif) fonts of the same typeface, if both are available. Google Fonts has several such typeface pairs choose from, e.g.:
- Alegreya and Alegreya Sans
- IBM Plex and IBM Plex Sans
- Inria Serif and Inria Sans
- Noto Serif and Noto Sans
- PT Serif and PT Sans
- Roboto and Roboto Slab
- Source Sans 3 and Source Serif 4
If your chosen prose typeface has an x-height much different than your chosen base typeface, then font-size-adjust can help.
If your base typeface has thicker strokes so that it “looks darker” than your prose typeface, then you may want to set your base font-weight slightly lighter. Variable fonts with font weight can allow fine tuning.
For inspiration:
- Pairing Typefaces (Google Fonts article)
- fontpairings.com
- fontpair.co
- fontjoy.com
- Pair & Compare
- Top 50 Google Font Pairings [Handpicked by Pro Designers] (Pagecloud)
Base font size
The default font size in Baselayer is 100% for small viewports. This sets the value of 1rem—that is 16px on most browsers (in 2025).
:root {
--t-base: 100%;
}
Typographic block elements
Previously in Baselayer, typographic blocks had zero top margin, and a small bottom margin—following the traditional Bootstrap CSS. But since Baselayer v.3.7.x, all block elements including typographic blocks have no built-in margin (margin is removed in the CSS reset). This gives you full control of whitespace in your design of components.
The typographic block t-flow utility
Then I discovered a YouTube video by Kevin Powell in which he flips the typographic block whitespace to become a top margin and sets it using ems, so that it is bigger for bigger font sizes (headings). This enabled the typographic styles to be simplified.
Subsequently I found Andy Bell’s typographic flow utility class to be a further improvement, enabling you to add back typographic whitepace (as top margin using ems) only where you need it in your prose or long read content areas. So now this t-flow utility has been added to Baselayer since v.3.7.x.
I have also added an override for situaions where you may have a sub-sub heading immediately following a sub-heading. The top margin of the lower level heading is reduced.
/* In reset.css */
* {
margin: 0;
padding: 0;
}
/* In typography.css */
.t-flow > * + *:not(hr) {
margin-top: var(--s-t-flow, 1em);
}
:where(h1, h2, h3, h4, h5)
+ :where(h2, h3, h4, h5, h6) {
margin-top: var(--s-between-headings, --s-1) !important;
}
Headings
Heading sizes are set a simple typographic scale (but set in em, not pt or px). The font size for h6 and utility class .h6 is not increased by this scale (remains the same as the base font size).
Example headings (using heading-size utility classes):
Heading h1
Heading h2
Heading h3
Heading h4
Heading h5
Heading h6
:root {
--h1: 2.5em;
--h2: 1.875em;
--h3: 1.5em;
--h4: 1.25em;
--h5: 1.125em;
--h6: 1em;
}
| Heading Level | Font Size in em | Equivalent Size in px |
|---|---|---|
| h6 | 1 | 16 |
| h5 | 1.125 | 18 |
| h4 | 1.25 | 20 |
| h3 | 1.5 | 24 |
| h2 | 1.875 | 30px |
| h1 | 2.5 | 40px |
Notes on headings in Baselayer:
- Headings font sizes set in the variables file (see above).
- Headings font family set using
--h-ff: inherit. This has been done so that you can use the variable to override it — your headings don’t need to be the same typeface as your paragraphs. - Headings font weight is set using
--h-fw: var(--t-semibold)— which you can also override. - Headings line heights set using the formula
1em + 0.5rem. - The matching heading size utility classes
h2toh6only affect font-size. They do not include font-weight styling. - The CSS reset removes margin and padding from headings. Typographic block spacing can be controlled by the
t-flow(see the typographic block t-flow utility). You could also incorporate thet-flowstyling directly into your<article>tag in your design layout, the typographic block elements are its immediate children.
:root {
--h-ff: inherit; /* headings font-family */
--h-fw: var(--t-semibold);
--h-lh: calc(1em + 0.5rem);
}
Block quotes
Baselayer styles <blockquote> tags with some inline (x-axis) padding, to give the effect of indentation. This inline padding is set using the container-responsive spacing variable --s-4 so that it increases if there is more available width.
Otherwise, blockquotes have the same as paragraph styling.
Lorem ipsum dolor sit amet, adipiscing honestatis ius ut, nisl consulatu pro in. Imperdiet evertitur no usu, his te suavitate salutatus. Nullam ridens deterruisset an duo. Cum harum insolens ei, cum probo placerat praesent et.
Lists
In Baselayer ordered <ol> and unordered <ul> have a small amount of left padding, to give them a left indent.
- Ordered item one
- Ordered item two
- Ordered item two child one
- Ordered item two child two
- Ordered item three
<ol>
<li>Ordered item one</li>
<li>Ordered item two
<ol>
<li>Ordered item two child one</li>
<li>Ordered item two child two</li>
</ol>
</li>
<li>Ordered item three</li>
</ol>
- Unordered item
- Unordered item
- Unordered item child
- Unordered item child
- Unordered item
<ul>
<li>Unordered item</li>
<li>Unordered item
<ul>
<li>Unordered item child</li>
<li>Unordered item child</li>
</ul>
</li>
<li>Unordered item</li>
</ul>
For definition lists (dictionary lists), the title is bold and the definition data item is indented with the same left padding as for the lists (see above).
- Definition list title
- Definition list data
- Definition list title
- Definition list data
<dl>
<dt>Definition list title</dt>
<dd>Definition list data</dd>
<dt>Definition list title</dt>
<dd>Definition list data</dd>
</dl>
Links and menus
New in Baselayer v.3.8.0: link text is now darkened on hover by mixing in a little black, or lightened for the dark theme. And Baselayer has reverted to the default link underline (the opinionated modification of position and thickness has been removed).
Example:
- This is a link to nowhere as a demonstration
Link utilities
For accessability, links within the main content area usually have underlines as a visual cue.
- This link has no underline —
t-underline-none - This link has no underline unless hovered —
t-underline-hover-only
It needs to be visually obvious that a link is a link. Therefore the browser default, and the best practice for accessability, is to have links indicated by an underline (and the browser default color of links is usually blue). However, n other locations such as navigation menus, the unlerline may not be necessary; but you will want to add in other visual indicators.
It is also best practice for accessibility and SEO reasons to use semantic HTML tags on menus, and and why we should place navigation menus in their expected locations: in sitewide menu-bars, sidebars, and footers.
Within the main content area, if you remove the link underline, you will want to add another visual cue e.g. by changing the background color on hover (as is done in “links styled as pseudo buttons”).
Panel links
There may be situations where you have a link that contains some accompanying text that you don’t want to receive the <a href=""> (link) underline. Examples where this may be used: media object, card or hero component, where you want the whole thing to behave like a “big button”. for these, you need the panel-link and panel-link-title combo classes.
You will also want to style the outer <a href=""> e.g. with the block utility (for display:block), and add more styles to this inner element text, so that it is not the same color as the link label.
You will also want to style the other element(s) text that’s not the panel-link-title with a different color, e.g. t-gray t-900 dark:t-100.
<a class="block my-3 p-3 bg-gray bg-100 dark:bg-900 panel-link" href="#/">
<div class="panel-link-title">Link label (title)</div>
<div class="t-gray t-900 dark:t-100">Lorem ipsum ...</div>
</a>
Another example: here t-underline-hover-only has also been added to the <a href="">:
<a class="block my-3 p-3 bg-gray bg-100 dark:bg-900 panel-link t-underline-hover-only" href="#/">
<div class="panel-link-title">Link label (title)</div>
<div class="t-gray t-900 dark:t-100">Lorem ipsum ...</div>
</a>
Menus
nav/<menu>/<menuitem>
The semantic <nav> tag has no styling of its own, other than its being a block element (similar to a div). Use it to organize your major navigation blocks of links (whether to other pages in your website, or “on this page”).
Semantic <menu> tags are treated by the browser as no different to an <ul>, but they are announced as a menu by screen readers. Baselayer gives <menu>, <ul>, and <ol> the same styling: some left padding and bottom margin (and you can remove these you don't require them, using spacing utility classes).
Using an <li> within a <menu> will give you bulletpoint block items. This will be OK for sidebars and dropdown menus, for example, but there’s also the <menuitem> tag. This is an inline tag (like a <span> but semantic for menu items), and it doesn’t come with a bulletpoint.
This is what you get if you add no styling, other than the Baselayer defaults (but of course you will want to add more styling):
<nav>
<menu>
<menuitem><a href="">Menu item 1</a></menuitem>
<menuitem><a href="">Menu item 2</a></menuitem>
<menuitem><a href="">Menu item 3</a></menuitem>
</menu>
</nav>
Notes:
- Use
<nav>to encapsulate your main navigation and “on this page” navigation. Navs can contain more than one menu, e.g. in dropdowns. The website or brand logo (with a link to the homepage) can be situated within the top<nav>but outside of the menu. - The
<menu>tag can be used in other contexts, e.g. for a list of related links to other websites, or for a set of user interface controls in a web application. - Inside a
<menuitem>can be a link, a button, or other interactive element.
If you want a group of links (in a menu) to not have underlines, or to have underlines only when hovered, the following wrapper classes are available in Baselayer:
links-underline-nonelinks-underline-hover-only
And now, with a few other Baselayer utilities, you have a menubar:
<nav>
<menu class="p-2 flex flex-wrap gap-4 bg-gray bg-100 dark:bg-900 links-underline-none">
<menuitem><a class="t-semibold t-gray t-700 dark:t-300 hover:t-reversi" href="">Menu item 1</a></menuitem>
<menuitem><a class="t-semibold t-gray t-700 dark:t-300 hover:t-reversi" href="">Menu item 2</a></menuitem>
<menuitem><a class="t-semibold t-gray t-700 dark:t-300 hover:t-reversi" href="">Menu item 3</a></menuitem>
</menu>
</nav>
Or a menu for a sidebar or footer buffet:
<nav>
<menu class="pl-0 flex flex-column gap-2 links-underline-hover-only">
<menuitem><a href="">Menu item 1</a></menuitem>
<menuitem><a href="">Menu item 2</a></menuitem>
<menuitem><a href="">Menu item 3</a></menuitem>
</menu>
</nav>
Text alignment
t-right,t-center, andt-left
In addition to the three simple text alignment classes above, Baselayer also has several container-query responsive variants, for xs:, sm:, md:, and lg: container breakpoints widths (corresponding to the four container breakpoints for the Baselayer grid layout) system).
Example: The hearing in the card below is left aligned by default, but becomes center aligned when its container is xs (512px) wide and above, using xs:t-center:
A title
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Magni rem animi quaerat accusantium illum architecto, nemo, ex harum voluptatum adipisci eum blanditiis dolorum. Natus debitis quisquam, expedita accusantium quos cumque?
For more information on Baselayer container queries, see contaner query contexts and container query powered layouts.
Tables
Baselayer tables are set using the .table class.
- Table headers
<th>are bold. - Table cells
<th>and<td>have a border set byvar(--b-1)– the same detail as<hr>and border utility classes. - Table cell paddings are set by
--p-cell(see decoration spacing extras) - All cell content is left-aligned. You can change that on the whole
<table>or on a per-cell basis using the text alignment classes. - Table cell widths normally adjust to accommmodate the content. Adding the optional modifier
table-fixedwill force all the cells to have the same (fixed) width.
| Table Header 1 | Table Header 2 | Table Header 3 |
|---|---|---|
| Table content 1.1 | Table content 2.1 | Table content 3.1 |
| Table content 1.2 | Table content 2.2 | Table content 3.2 |
| Table content 1.3 | Table content 2.3 | Table content 3.3 |
| Table content 1.4 | Table content 2.4 | Table content 3.4 |
<table class="table">
<caption>This is a Table Caption</caption>
<thead>
<tr>
<th>Table Header 1</th>
<th>Table Header 2</th>
<th>Table Header 3</th>
</tr>
</thead>
<tbody>
<tr>
<td>Table content 1.1</td>
<td>Table content 2.1</td>
<td>Table content 3.1</td>
</tr>
<tr>
<td>Table content 1.2</td>
<td>Table content 2.2</td>
<td>Table content 3.2</td>
</tr>
...
</tbody>
</table>
Making wide tables responsive
If you have a lot of content in your table, it will probably break your page layout on small viewports (e.g. phones). The simplest way to make a table “responsive” is to wrap your table in a DIV with the overflow-x class to make it horizontally scrollable.
| Column title | Column title | Column title | Column title | Column title | Column title | Column title | Column title | Column title | Column title | Column title | Column title | Column title | Column title | Column title |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Table cell content | Table cell content | Table cell content | Table cell content | Table cell content | Table cell content | Table cell content | Table cell content | Table cell content | Table cell content | Table cell content | Table cell content | Table cell content | Table cell content | Table cell content |
<div class="overflow-x">
<table class="table">
...
</table>
</div>
Controlling text size
The base font size is 100% (16px default). Additionally Baselayer includes all the following font-size multipliers.
Note: Baselayer font size multipliers are sent in em relative sizes, to enable the multipliciation. This means that if you nest them, you multiply the multiplication. There are two things you need to beware of:
- The resulting font size can be too big for small devices; check what your titles and hero messaging looks like on phones. The responsive font-size multipliers are designed to help you not make tech too bug for phones.
- If you want some words of a title to be in smaller text, you need
t-smfor that portion, not e.g.h2insideh1because, counterintuitively, that will make the text larger not smaller.
Inline font size multipliers
The t-sm class will reduce text to 0.75em – good for small-print, footnotes, or reducing the size of part of the text within headings.
The t-lg class will enlarge text to 1.25em — good for a lead paragraph, large button, or important messaging.
t-sm text, base text, t-lg text
Block font size multipliers
Classes h1 through h6 will resize text the same amount as for their respective heading tag sizes — use these when you want to make text bigger (or large text smaller) without adversely affecting accessibility/ SEO heading hierarchy.
h6 heading size is the same as base size, but in em
h5 heading size
h4 heading size is the same as t-lg
h3 heading size
h2 heading size
h1 heading size
Responsive font size multipliers
In small container (or viewport) widths, these three utilities below do nothing. But with wider stace available, they use a CSS clamp() to ramp up the size to a maximum.
- The
t-long-readwrapping class ramps text up from 1× to 1.25× depending on container size. - The
t-comfortwrapping class ramps text up from 1× to 1.625× depending on container size. - The
t-displaywrapping class ramps text up from 1× to 2× depending on container size.
The calculated max pixel sizes are as follows:
| Type size | Paragraph max font size (px) | H1 max font size (px) |
|---|---|---|
| Base | 16px | 40px |
t-long-read |
20px | 50px |
t-comfort |
26px | 65px |
t-display |
32px | 80px |
There’s a demo of these text size utilities in examples.
Code
<code> tags have monospaced text (set by --t-mono) over a white or black background (in light or dark modes), with a thin border and a little padding to improve readability.
If the <code> tag is wrapped in a <pre> tag, then it becomes a block level element with more padding, a max-width of 100%, and y-axis overflow scrolling.
Other typographic utility classes
Besides those already introduced, Baselayer also has utility classes for:
t-sm(or use the<small>HTML tag) — decrease font-size to 0.8emt-thin,t-normal,t-semibold,t-bold,t-heavy— font weightst-italic— font style italict-balance— balances word-wrap, so that e.g. long headings don't have orphanst-uppercase— text transform to capitalst-nowrap— prevents text wrapping (spaces behave as non-breaking spaces)
Note: links can also be styled as though they are buttons using the btn utility class. See buttons.