Interactive web component for testing and previewing fonts. No dependencies. Built for Fountain but works anywhere.
- Live text preview with editable text
- Font style/weight variant selector
- Adjustable size, line height, and letter spacing
- Variable font axis sliders
- OpenType feature toggles
- Custom sample texts
- Lazy font loading via font-loader (optional)
- Themeable via CSS custom properties and
::part() - i18n support
<font-tester font-family="my-font" controls="uppercase,direction,text-align,sample-text,font-size,line-height,letter-spacing,font-style,opentype">
<!-- Font style variants -->
<font-style name="Regular" family="my-font" weight="400" style="normal" default></font-style>
<font-style name="Italic" family="my-font" weight="400" style="italic"></font-style>
<font-style name="Bold" family="my-font" weight="700" style="normal"></font-style>
<!-- OpenType features -->
<opentype-feature code="kern" name="Kerning"></opentype-feature>
<opentype-feature code="liga" name="Ligatures"></opentype-feature>
<opentype-feature code="smcp" name="Small Caps"></opentype-feature>
<!-- Sample texts (first is shown by default; named ones appear in the dropdown) -->
<sample-text>The quick brown fox jumps over the lazy dog</sample-text>
<sample-text name="Alphabet">ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz</sample-text>
<sample-text name="Numbers">0123456789</sample-text>
</font-tester>For variable fonts, use var-axes in controls and declare axes via <font-variation-axis> markers instead of <font-style> variants. The two are mutually exclusive.
<font-tester font-family="my-variable-font" controls="uppercase,direction,text-align,sample-text,font-size,line-height,var-axes">
<font-variation-axis tag="wght" name="Weight" min="100" max="900" default="400"></font-variation-axis>
<font-variation-axis tag="wdth" name="Width" min="75" max="125" default="100"></font-variation-axis>
<font-variation-axis tag="OPSZ" name="Optical size" min="6" max="72" default="14" step="0.1"></font-variation-axis>
</font-tester>Each axis renders as a range slider. All axes compose into a single font-variation-settings value applied to the preview text.
Axis attributes: tag (4-char axis tag, required), name (label), min, max, default, step.
Layout is controlled via CSS custom properties, set on font-tester itself (not on var-axes-controls. Descendant selectors can’t reach it, since it lives inside font-tester’s shadow DOM):
font-tester {
--axes-direction: row; /* set to column to stack axes vertically instead of wrapping */
--axes-wrap: wrap;
--axis-gap: 20px;
}Add the fit-width attribute to automatically scale the font size so the preview text fills the full width of the display area. Useful for single-word or headline display testing.
<font-tester font-family="my-font" fit-width="each">
<sample-text>Stockholm</sample-text>
</font-tester>The font size is calculated using the browser’s layout engine, so letter-spacing, variable axes, and OpenType features are all accounted for. A 2px safety margin is applied to prevent overflow.
Two modes are available via the attribute value:
| Value | Behaviour |
|---|---|
each (or bare fit-width) |
Recalculates on every font load, including style/weight changes |
once |
Fits once on the first font load, then leaves the size alone |
once is useful when you want the initial size set automatically but still want full control of the font-size slider afterwards.
If a font-size slider is included in controls, it stays in sync. It updates to show the calculated value and can still be used to manually override it. With each, the fit is restored on the next font change.
fit-width works with or without sample-text. If sample-text is omitted, the first <sample-text> marker is shown directly without a dropdown.
Set starting values for the typography controls directly on <font-tester>:
<font-tester
font-family="my-font"
font-size="64px"
line-height="1.2"
letter-spacing="0.02em"
text-align="center"
direction="rtl"
>These are read once at connect time, sliders initialize at these values and the display renders with them applied. They are not reactive after mount.
Comma-separated list of controls to show, in the order you want them to appear. Omit the attribute entirely to show every control, in the default order below.
| Value | Control |
|---|---|
uppercase |
Uppercase toggle button |
direction |
LTR / RTL toggle |
text-align |
Left / center / right alignment toggle |
sample-text |
Sample text dropdown |
font-style |
Style/weight variant dropdown (static fonts) |
opentype |
OpenType features dialog |
var-axes |
Variable font axis sliders |
font-size |
Font size slider |
line-height |
Line height slider |
letter-spacing |
Letter spacing slider |
Each token is inserted into the DOM in the order it appears in controls. Nothing is reordered afterwards with CSS. Since tab order follows DOM order by default, the order you write here is the keyboard tab order, with no tabindex attribute involved anywhere:
<!-- Tabs in this order: opentype → font-style → sample text → uppercase → alignment → font size → line height → letter spacing -->
<font-tester controls="opentype,font-style,sample-text,uppercase,text-align,font-size,line-height,letter-spacing">This is independent of visual position, which is controlled separately via CSS Grid (grid-area/grid-column/grid-row through ::part(), see Theming below). You can move where something appears on screen without changing its place in the tab sequence, and vice versa.
<script type="module" src="font-tester.min.js"></script>Register fonts in CSS as usual. Pass the family name via the font-family attribute:
<style>
@font-face {
font-family: 'my-font';
src: url('/fonts/my-font.woff2') format('woff2');
}
</style>
<font-tester font-family="my-font">...</font-tester>font-loader loads fonts on demand via the FontFace API as testers enter the viewport, no @font-face CSS needed.
<script type="importmap">
{
"imports": {
"font-loader": "/js/font-loader.min.js"
}
}
</script>
<script type="module" src="/js/font-loader.min.js"></script>
<script type="module" src="/js/font-tester-with-loader.min.js"></script>The font-tester-with-loader.min.js bundle includes font-tester-loader.js, which bridges the two libraries. Use font-tester.min.js (without the loader) if you’re using @font-face.
All styling is exposed via CSS custom properties and ::part() selectors. Custom properties are set on font-tester itself and they cascade through every shadow boundary into the sub-components automatically. Descendant selectors like font-tester font-display { } do not work, since the sub-components live inside font-tester’s shadow DOM, not its light DOM. Copy font-tester-theme.css as a starting point:
font-tester {
--container-max-width: 1200px;
--display-border-color: #e0e0e0;
--display-background: #f9f9f9;
--text-color: #111;
/* ... */
}
font-tester text-controls::part(button) {
border-radius: 0;
}See FONT-TESTER-CSS-GUIDE.md or font-tester-theme.css for the full custom-property reference. A few groups of properties are shared across components (with different fallback values in each place), so styling one can affect the others unless overridden with a more specific ::part() selector:
--control-*— buttons (text-controls, the OpenType trigger) and eachstyle-controlsrow and eachvar-axes-controlsrow (background/border/border-radius/padding).border/border-radiusalso reach dropdowns — see below.--select-*—sample-text-selectorandfont-style-selector.--select-border/--select-border-radiusfall back through--control-border/--control-border-radiusbefore their own default, so setting--control-borderalone also affects dropdowns unless--select-borderis set specifically--label-*—font-style-selectorandsample-text-selector--slider-*/--value-*—style-controlsandvar-axes-controls, identically in both
Parts can also be used to hide controls that are enabled in controls but should not be shown in a particular instance:
font-tester::part(sample-text-control) {
display: none;
}Set window.FontTesterConfig before the script loads to override global defaults:
<script>
window.FontTesterConfig = {
defaultOnFeatures: ['kern', 'liga']
};
</script>
<script type="module" src="font-tester.min.js"></script>The list of OpenType feature codes that are toggled on by default when listed as <opentype-feature> markers. Defaults to the set of features browsers enable automatically:
['kern', 'liga', 'calt', 'locl', 'ccmp', 'mark', 'mkmk', 'rlig']Features in this list don’t need a default attribute on their marker. They’re on unless the user toggles them off. The default attribute still works as an explicit per-feature override.
Can also be overridden at runtime after import:
import { OpentypeFeatures } from './font-tester.min.js';
OpentypeFeatures.defaultOnFeatures = ['kern', 'liga'];UI labels can be translated via an inline or external JSON file. See FONT-TESTER-I18N-README.md for setup and the full list of translation keys.
<script id="font-tester-i18n" type="application/json">
{
"sv": {
"textControls": { "uppercaseButton": "Versaler" }
}
}
</script>
<font-tester lang="sv">...</font-tester>Two reasons. One is to make it feel native to HTML. The other is that each instance gets its own shadow root, so the browser recalculates styles in isolation. With 20+ instances on a page that makes a meaningful difference.
MIT
