Typography
Overview
The typography composables help you create comprehensive type systems with minimal code. They generate typography-related variables that can be easily referenced throughout your application, enabling flexible theming and consistent text styling across different components and contexts.
Why use typography composables?
Typography composables help you:
- Centralize typography definitions: Define all your font families, sizes, weights, line heights, and letter spacing in one place.
- Enable flexible theming: Override typography values to instantly update text styles across your application.
- Maintain consistency: Use semantic names to ensure consistent typography usage throughout your design system.
- Create harmonious scales: Integrate with modular scales to generate mathematically proportional type systems.
- Simplify maintenance: Update typography values in one place instead of searching through your codebase.
useFontFamily
The useFontFamily() function creates a set of font family variables from a simple object of font stack definitions.
Default Font Family Values
Styleframe provides carefully chosen default font family values that you can use out of the box:
import { styleframe } from 'styleframe';
import { useFontFamily } from '@styleframe/theme';
const s = styleframe();
const {
fontFamily,
fontFamilyBase,
fontFamilyPrint,
fontFamilyMono,
} = useFontFamily(s);
export default s;
:root {
--font-family--base: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;
--font-family--print: 'Georgia', 'Times New Roman', 'Times', serif;
--font-family--mono: 'SFMono-Regular', Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
--font-family: var(--font-family--base);
}
The default values include:
base: System font stack for optimal performance and native appearanceprint: Serif font stack for print media and article contentmono: Monospace font stack for code and technical contentdefault: Referencesbaseby default
default key for your primary font family. It will create a variable named --font-family without any suffix, making it the natural choice for body text and general use.Creating Custom Font Family Variables
You can provide completely custom font family values:
import { styleframe } from 'styleframe';
import { useFontFamily } from '@styleframe/theme';
const s = styleframe();
const {
fontFamily,
fontFamilyMono,
fontFamilySerif,
} = useFontFamily(s, {
default: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif",
mono: "'SFMono-Regular', Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace",
serif: "'Georgia', 'Times New Roman', 'Times', serif",
} as const);
export default s;
:root {
--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;
--font-family--mono: 'SFMono-Regular', Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
--font-family--serif: 'Georgia', 'Times New Roman', 'Times', serif;
}
Extending the Default Font Family Values
You can extend the defaults with additional custom font families:
import { styleframe } from 'styleframe';
import { useFontFamily, defaultFontFamilyValues } from '@styleframe/theme';
const s = styleframe();
const {
fontFamily,
fontFamilyBase,
fontFamilyPrint,
fontFamilyMono,
fontFamilyDisplay,
} = useFontFamily(s, {
...defaultFontFamilyValues,
display: "'Inter', -apple-system, BlinkMacSystemFont, sans-serif",
} as const);
export default s;
:root {
--font-family--base: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;
--font-family--print: 'Georgia', 'Times New Roman', 'Times', serif;
--font-family--mono: 'SFMono-Regular', Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
--font-family--display: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
--font-family: var(--font-family--base);
}
useFontSize
The useFontSize() function creates a set of font size variables.
import { styleframe } from 'styleframe';
import { useFontSize } from '@styleframe/theme';
const s = styleframe();
const {
fontSize,
fontSizeXs,
fontSizeSm,
fontSizeMd,
fontSizeLg,
fontSizeXl,
} = useFontSize(s, {
default: '1rem',
xs: '0.75rem',
sm: '0.875rem',
md: '1rem',
lg: '1.25rem',
xl: '1.5rem',
} as const);
export default s;
:root {
--font-size: 1rem;
--font-size--xs: 0.75rem;
--font-size--sm: 0.875rem;
--font-size--md: 1rem;
--font-size--lg: 1.25rem;
--font-size--xl: 1.5rem;
}
Integration with useMultiplier
The real power of useFontSize comes when combined with useMultiplier() and modular scales. This allows you to create mathematically harmonious type systems:
import { styleframe } from 'styleframe';
import { useScale, useScalePowers, useMultiplier, useFontSize } from '@styleframe/theme';
const s = styleframe();
// Use Perfect Fourth scale (1.333) for clear typographic hierarchy
const { scale } = useScale(s, 'perfect-fourth');
// Define base font size
const { fontSize } = useFontSize(s, { default: '1rem' });
// Create scale powers
const scalePowers = useScalePowers(s, scale, [-2, -1, 0, 1, 2, 3, 4, 5]);
// Generate font size scale automatically
const {
fontSizeXs,
fontSizeSm,
fontSizeMd,
fontSizeLg,
fontSizeXl,
fontSize2xl,
fontSize3xl,
fontSize4xl,
} = useMultiplier(s, fontSize, {
xs: scalePowers[-2], // ~0.56rem
sm: scalePowers[-1], // ~0.75rem
md: scalePowers[0], // 1rem (base)
lg: scalePowers[1], // ~1.33rem
xl: scalePowers[2], // ~1.78rem
'2xl': scalePowers[3], // ~2.37rem
'3xl': scalePowers[4], // ~3.16rem
'4xl': scalePowers[5], // ~4.21rem
});
export default s;
:root {
--scale--perfect-fourth: 1.333;
--scale: var(--scale--perfect-fourth);
--scale-power---2: calc(1 / var(--scale) / var(--scale));
--scale-power---1: calc(1 / var(--scale));
--scale-power--0: 1;
--scale-power--1: var(--scale);
--scale-power--2: calc(var(--scale) * var(--scale));
--scale-power--3: calc(var(--scale) * var(--scale) * var(--scale));
--scale-power--4: calc(var(--scale) * var(--scale) * var(--scale) * var(--scale));
--scale-power--5: calc(var(--scale) * var(--scale) * var(--scale) * var(--scale) * var(--scale));
--font-size: 1rem;
--font-size--xs: calc(var(--font-size) * var(--scale-power---2));
--font-size--sm: calc(var(--font-size) * var(--scale-power---1));
--font-size--md: calc(var(--font-size) * var(--scale-power--0));
--font-size--lg: calc(var(--font-size) * var(--scale-power--1));
--font-size--xl: calc(var(--font-size) * var(--scale-power--2));
--font-size--2xl: calc(var(--font-size) * var(--scale-power--3));
--font-size--3xl: calc(var(--font-size) * var(--scale-power--4));
--font-size--4xl: calc(var(--font-size) * var(--scale-power--5));
}
The useMultiplier() function multiplies your base font size by the scale powers, creating a harmonious progression of sizes that maintain consistent proportional relationships.
Read more about design scales and take advantage of the flexibility they offer.
useMultiplier() with scales means you can change your entire type system's proportions by simply adjusting the scale ratio. Try different scales like Major Third (1.25) for more compact hierarchies or Perfect Fifth (1.5) for dramatic size differences.useFontWeight
The useFontWeight() function creates a set of font weight variables covering the standard range of weights.
Styleframe provides a comprehensive set of default font weight values:
import { styleframe } from 'styleframe';
import { useFontWeight } from '@styleframe/theme';
const s = styleframe();
const {
fontWeight,
fontWeightExtralight,
fontWeightLight,
fontWeightNormal,
fontWeightMedium,
fontWeightSemibold,
fontWeightBold,
fontWeightBlack,
fontWeightLighter,
fontWeightBolder,
fontWeightInherit,
} = useFontWeight(s);
export default s;
:root {
--font-weight--extralight: 200;
--font-weight--light: 300;
--font-weight--normal: normal;
--font-weight--medium: 500;
--font-weight--semibold: 600;
--font-weight--bold: bold;
--font-weight--black: 900;
--font-weight--lighter: lighter;
--font-weight--bolder: bolder;
--font-weight--inherit: inherit;
--font-weight: var(--font-weight--normal);
}
The default values include:
extralight: 200 - Ultra thin weightlight: 300 - Light weightnormal:normalkeyword (typically 400)medium: 500 - Medium weightsemibold: 600 - Semibold weightbold:boldkeyword (typically 700)black: 900 - Heaviest weightlighter:lighterkeyword (relative to parent)bolder:bolderkeyword (relative to parent)inherit: Inherits from parentdefault: Referencesnormalby default
Creating Custom Font Weight Variables
Override font weights to match your font's available weights:
import { styleframe } from 'styleframe';
import { useFontWeight } from '@styleframe/theme';
const s = styleframe();
const {
fontWeight,
fontWeightLight,
fontWeightRegular,
fontWeightMedium,
fontWeightBold,
} = useFontWeight(s, {
default: 400,
light: 300,
regular: 400,
medium: 500,
bold: 700,
} as const);
export default s;
:root {
--font-weight: 400;
--font-weight--light: 300;
--font-weight--regular: 400;
--font-weight--medium: 500;
--font-weight--bold: 700;
}
Extending the Default Font Weight Values
You can keep the defaults and override specific values:
import { styleframe } from 'styleframe';
import { useFontWeight, defaultFontWeightValues } from '@styleframe/theme';
const s = styleframe();
const { fontWeight } = useFontWeight(s, {
...defaultFontWeightValues,
default: '@semibold'
} as const);
export default s;
:root {
--font-weight--extralight: 200;
--font-weight--light: 300;
--font-weight--normal: normal;
--font-weight--medium: 500;
--font-weight--semibold: 600;
--font-weight--bold: bold;
--font-weight--black: 900;
--font-weight--lighter: lighter;
--font-weight--bolder: bolder;
--font-weight--inherit: inherit;
--font-weight: var(--font-weight--semibold);
}
useFontStyle
The useFontStyle() function creates a set of font style variables for controlling text styling like italic and oblique.
Styleframe provides all standard CSS font style values:
import { styleframe } from 'styleframe';
import { useFontStyle } from 'styleframe/theme';
const s = styleframe();
const {
fontStyle,
fontStyleItalic,
fontStyleOblique,
fontStyleNormal,
fontStyleInherit,
} = useFontStyle(s);
export default s;
:root {
--font-style--italic: italic;
--font-style--oblique: oblique;
--font-style--normal: normal;
--font-style--inherit: inherit;
--font-style: var(--font-style--normal);
}
The default values include:
italic: Italic style (uses true italic font if available)oblique: Oblique style (slanted version of normal font)normal: Normal upright styleinherit: Inherits from parentdefault: Referencesnormalby default
italic uses a true italic font variant if available, while oblique artificially slants the font. Most fonts provide italic variants, making italic the preferred choice for emphasis.Creating Custom Font Style Variables
Define custom font style values for specific needs:
import { styleframe } from 'styleframe';
import { useFontStyle } from 'styleframe/theme';
const s = styleframe();
const {
fontStyle,
fontStyleItalic,
fontStyleSlanted,
} = useFontStyle(s, {
default: 'normal',
italic: 'italic',
slanted: 'oblique 15deg',
} as const);
export default s;
:root {
--font-style--italic: italic;
--font-style--slanted: oblique 15deg;
--font-style: normal;
}
Extending the Default Font Style Values
You can extend the defaults with additional custom values:
import { styleframe } from 'styleframe';
import { useFontStyle, defaultFontStyleValues } from 'styleframe/theme';
const s = styleframe();
const { fontStyle } = useFontStyle(s, {
...defaultFontStyleValues,
default: '@italic'
} as const);
export default s;
:root {
--font-style--italic: italic;
--font-style--oblique: oblique;
--font-style--normal: normal;
--font-style--inherit: inherit;
--font-style: var(--font-style--italic);
}
Updating the Default Font Style Variable
You can override the default font style value after creating it:
import { styleframe } from 'styleframe';
import { useFontStyle } from 'styleframe/theme';
const s = styleframe();
const { variable } = s;
const { fontStyle } = useFontStyle(s);
// Override the default font style
variable(fontStyle, 'italic');
export default s;
:root {
--font-style--italic: italic;
--font-style--oblique: oblique;
--font-style--normal: normal;
--font-style--inherit: inherit;
--font-style: italic;
}
useLineHeight
The useLineHeight() function creates a set of line height variables for controlling vertical rhythm and text readability.
Styleframe provides carefully balanced default line height values:
import { styleframe } from 'styleframe';
import { useLineHeight } from '@styleframe/theme';
const s = styleframe();
const {
lineHeight,
lineHeightTight,
lineHeightSnug,
lineHeightNormal,
lineHeightRelaxed,
lineHeightLoose,
} = useLineHeight(s);
export default s;
:root {
--line-height--tight: 1.2;
--line-height--snug: 1.35;
--line-height--normal: 1.5;
--line-height--relaxed: 1.65;
--line-height--loose: 1.9;
--line-height: var(--line-height--normal);
}
The default values include:
tight: 1.2 - For headings and display textsnug: 1.35 - For compact UI textnormal: 1.5 - For body text (optimal readability)relaxed: 1.65 - For longer reading passagesloose: 1.9 - For maximum spacing and accessibilitydefault: Referencesnormalby default
1.5 means 1.5 times the font size, which scales proportionally as font sizes change.Creating Custom Line Height Variables
Define custom line heights for specific design needs:
import { styleframe } from 'styleframe';
import { useLineHeight } from '@styleframe/theme';
const s = styleframe();
const {
lineHeight,
lineHeightHeading,
lineHeightBody,
lineHeightArticle,
} = useLineHeight(s, {
default: 1.5,
heading: 1.2,
body: 1.5,
article: 1.75,
} as const);
export default s;
:root {
--line-height: 1.5;
--line-height--heading: 1.2;
--line-height--body: 1.5;
--line-height--article: 1.75;
}
Updating the Default Line Height Variable
You can override the default line height value after creating it:
import { styleframe } from 'styleframe';
import { useLineHeight } from '@styleframe/theme';
const s = styleframe();
const { variable } = s;
const { lineHeight } = useLineHeight(s);
// Override the default line height
variable(lineHeight, 1.65);
export default s;
:root {
--line-height--tight: 1.2;
--line-height--snug: 1.35;
--line-height--normal: 1.5;
--line-height--relaxed: 1.65;
--line-height--loose: 1.9;
--line-height: 1.65;
}
useLetterSpacing
The useLetterSpacing() function creates a set of letter spacing (tracking) variables for fine-tuning text appearance.
Styleframe provides balanced default letter spacing values:
import { styleframe } from 'styleframe';
import { useLetterSpacing } from '@styleframe/theme';
const s = styleframe();
const {
letterSpacing,
letterSpacingTighter,
letterSpacingTight,
letterSpacingNormal,
letterSpacingWide,
letterSpacingWider,
} = useLetterSpacing(s);
export default s;
:root {
--letter-spacing--tighter: -0.05em;
--letter-spacing--tight: -0.025em;
--letter-spacing--normal: normal;
--letter-spacing--wide: 0.05em;
--letter-spacing--wider: 0.1em;
--letter-spacing: var(--letter-spacing--normal);
}
The default values include:
tighter: -0.05em - Very tight spacing for large display texttight: -0.025em - Tight spacing for headingsnormal:normalkeyword - Default browser spacingwide: 0.05em - Loose spacing for small text or all-capswider: 0.1em - Very loose spacing for labels and UI textdefault: Referencesnormalby default
em units so it scales proportionally with font size. Negative values tighten spacing, positive values loosen it.Creating Custom Letter Spacing Variables
Define custom letter spacing for specific typography styles:
import { styleframe } from 'styleframe';
import { useLetterSpacing } from '@styleframe/theme';
const s = styleframe();
const {
letterSpacing,
letterSpacingHeading,
letterSpacingBody,
letterSpacingLabel,
} = useLetterSpacing(s, {
default: 'normal',
heading: '-0.02em',
body: 'normal',
label: '0.08em',
} as const);
export default s;
:root {
--letter-spacing: normal;
--letter-spacing--heading: -0.02em;
--letter-spacing--body: normal;
--letter-spacing--label: 0.08em;
}
Extending the Default Letter Spacing Values
You can extend the defaults with additional custom values:
import { styleframe } from 'styleframe';
import { useLetterSpacing, defaultLetterSpacingValues } from '@styleframe/theme';
const s = styleframe();
const {
letterSpacing,
letterSpacingTighter,
letterSpacingTight,
letterSpacingNormal,
letterSpacingWide,
letterSpacingWider,
letterSpacingWidest,
} = useLetterSpacing(s, {
...defaultLetterSpacingValues,
widest: '0.15em',
} as const);
export default s;
:root {
--letter-spacing--tighter: -0.05em;
--letter-spacing--tight: -0.025em;
--letter-spacing--normal: normal;
--letter-spacing--wide: 0.05em;
--letter-spacing--wider: 0.1em;
--letter-spacing--widest: 0.15em;
--letter-spacing: var(--letter-spacing--normal);
}
Using Typography Variables
Once created, typography variables can be combined to create complete text styles:
import { styleframe } from 'styleframe';
import { useFontFamily, useFontSize, useFontWeight, useLineHeight, useLetterSpacing } from '@styleframe/theme';
const s = styleframe();
const { ref, selector } = s;
// Define typography variables
const { fontFamily, fontFamilyMono } = useFontFamily(s);
const { fontSizeXl, fontSize2xl, fontSize3xl } = useFontSize(s, {
xl: '1.25rem',
'2xl': '1.5rem',
'3xl': '2rem',
} as const);
const { fontWeightNormal, fontWeightSemibold, fontWeightBold } = useFontWeight(s);
const { fontStyleNormal, fontStyleItalic } = useFontStyle(s);
const { lineHeightTight, lineHeightNormal } = useLineHeight(s);
const { letterSpacingTight, letterSpacingWide } = useLetterSpacing(s);
// Apply to elements
selector('h1', {
fontFamily: ref(fontFamily),
fontSize: ref(fontSize3xl),
fontWeight: ref(fontWeightBold),
fontStyle: ref(fontStyleNormal),
lineHeight: ref(lineHeightTight),
letterSpacing: ref(letterSpacingTight),
});
selector('body', {
fontFamily: ref(fontFamily),
fontWeight: ref(fontWeightNormal),
fontStyle: ref(fontStyleNormal),
lineHeight: ref(lineHeightNormal),
});
selector('em, i', {
fontStyle: ref(fontStyleItalic),
});
selector('code', {
fontFamily: ref(fontFamilyMono),
fontSize: ref(fontSizeXl),
letterSpacing: ref(letterSpacingWide),
});
export default s;
:root {
--font-family--base: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;
--font-family--print: 'Georgia', 'Times New Roman', 'Times', serif;
--font-family--mono: 'SFMono-Regular', Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
--font-family: var(--font-family--base);
--font-size--xl: 1.25rem;
--font-size--2xl: 1.5rem;
--font-size--3xl: 2rem;
--font-weight--extralight: 200;
--font-weight--light: 300;
--font-weight--normal: normal;
--font-weight--medium: 500;
--font-weight--semibold: 600;
--font-weight--bold: bold;
--font-weight--black: 900;
--font-style--italic: italic;
--font-style--oblique: oblique;
--font-style--normal: normal;
--font-style--inherit: inherit;
--font-style: var(--font-style--normal);
--line-height--tight: 1.2;
--line-height--snug: 1.35;
--line-height--normal: 1.5;
--line-height--relaxed: 1.65;
--line-height--loose: 1.9;
--letter-spacing--tight: -0.025em;
--letter-spacing--wide: 0.05em;
}
h1 {
font-family: var(--font-family);
font-size: var(--font-size--3xl);
font-weight: var(--font-weight--bold);
font-style: var(--font-style--normal);
line-height: var(--line-height--tight);
letter-spacing: var(--letter-spacing--tight);
}
body {
font-family: var(--font-family);
font-weight: var(--font-weight--normal);
font-style: var(--font-style--normal);
line-height: var(--line-height--normal);
}
em, i {
font-style: var(--font-style--italic);
}
code {
font-family: var(--font-family--mono);
font-size: var(--font-size--xl);
letter-spacing: var(--letter-spacing--wide);
}
Examples
Complete Typography System
Here's a comprehensive example showing a full typography system with all composables working together:
import { styleframe } from 'styleframe';
import {
useFontFamily,
useFontSize,
useFontWeight,
useLineHeight,
useLetterSpacing,
useScale,
useScalePowers,
useMultiplier
} from '@styleframe/theme';
const s = styleframe();
const { ref, selector } = s;
// Font families
const { fontFamily, fontFamilyMono, fontFamilyDisplay } = useFontFamily(s, {
default: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
mono: "'JetBrains Mono', 'Fira Code', monospace",
display: "'Inter', sans-serif",
} as const);
// Font size with scale
const { scale } = useScale(s, 'perfect-fourth');
const { fontSize } = useFontSize(s, { default: '1rem' });
const scalePowers = useScalePowers(s, scale, [-2, -1, 0, 1, 2, 3, 4]);
const {
fontSizeXs,
fontSizeSm,
fontSizeMd,
fontSizeLg,
fontSizeXl,
fontSize2xl,
fontSize3xl,
} = useMultiplier(s, fontSize, {
xs: scalePowers[-2],
sm: scalePowers[-1],
md: scalePowers[0],
lg: scalePowers[1],
xl: scalePowers[2],
'2xl': scalePowers[3],
'3xl': scalePowers[4],
});
// Font weights
const { fontWeightNormal, fontWeightMedium, fontWeightSemibold, fontWeightBold } = useFontWeight(s);
// Font styles
const { fontStyleNormal, fontStyleItalic } = useFontStyle(s);
// Line heights
const { lineHeightTight, lineHeightNormal, lineHeightRelaxed } = useLineHeight(s);
// Letter spacing
const { letterSpacingTight, letterSpacingNormal, letterSpacingWide } = useLetterSpacing(s);
// Apply to elements
selector('body', {
fontFamily: ref(fontFamily),
fontSize: ref(fontSizeMd),
fontWeight: ref(fontWeightNormal),
lineHeight: ref(lineHeightNormal),
letterSpacing: ref(letterSpacingNormal),
});
selector('h1', {
fontFamily: ref(fontFamilyDisplay),
fontSize: ref(fontSize3xl),
fontWeight: ref(fontWeightBold),
lineHeight: ref(lineHeightTight),
letterSpacing: ref(letterSpacingTight),
});
selector('h2', {
fontFamily: ref(fontFamilyDisplay),
fontSize: ref(fontSize2xl),
fontWeight: ref(fontWeightBold),
lineHeight: ref(lineHeightTight),
letterSpacing: ref(letterSpacingTight),
});
selector('h3', {
fontFamily: ref(fontFamilyDisplay),
fontSize: ref(fontSizeXl),
fontWeight: ref(fontWeightSemibold),
lineHeight: ref(lineHeightTight),
});
selector('p', {
fontSize: ref(fontSizeMd),
lineHeight: ref(lineHeightRelaxed),
});
selector('small', {
fontSize: ref(fontSizeSm),
lineHeight: ref(lineHeightNormal),
});
selector('code', {
fontFamily: ref(fontFamilyMono),
fontSize: ref(fontSizeSm),
letterSpacing: ref(letterSpacingWide),
});
selector('.lead', {
fontSize: ref(fontSizeLg),
fontWeight: ref(fontWeightMedium),
lineHeight: ref(lineHeightRelaxed),
});
selector('.label', {
fontSize: ref(fontSizeXs),
fontWeight: ref(fontWeightMedium),
textTransform: 'uppercase',
letterSpacing: ref(letterSpacingWide),
});
export default s;
:root {
--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
--font-family--mono: 'JetBrains Mono', 'Fira Code', monospace;
--font-family--display: 'Inter', sans-serif;
--scale--perfect-fourth: 1.333;
--scale: var(--scale--perfect-fourth);
--font-size: 1rem;
--font-size--xs: calc(var(--font-size) * 1 / var(--scale) / var(--scale));
--font-size--sm: calc(var(--font-size) * 1 / var(--scale));
--font-size--md: calc(var(--font-size) * 0);
--font-size--lg: calc(var(--font-size) * var(--scale));
--font-size--xl: calc(var(--font-size) * var(--scale) * var(--scale));
--font-size--2xl: calc(var(--font-size) * var(--scale) * var(--scale) * var(--scale));
--font-size--3xl: calc(var(--font-size) * var(--scale) * var(--scale) * var(--scale) * var(--scale));
--font-weight--normal: normal;
--font-weight--medium: 500;
--font-weight--semibold: 600;
--font-weight--bold: bold;
--line-height--tight: 1.2;
--line-height--normal: 1.5;
--line-height--relaxed: 1.65;
--letter-spacing--tight: -0.025em;
--letter-spacing--normal: normal;
--letter-spacing--wide: 0.05em;
}
body {
font-family: var(--font-family);
font-size: var(--font-size--md);
font-weight: var(--font-weight--normal);
line-height: var(--line-height--normal);
letter-spacing: var(--letter-spacing--normal);
}
h1 {
font-family: var(--font-family--display);
font-size: var(--font-size--3xl);
font-weight: var(--font-weight--bold);
line-height: var(--line-height--tight);
letter-spacing: var(--letter-spacing--tight);
}
h2 {
font-family: var(--font-family--display);
font-size: var(--font-size--2xl);
font-weight: var(--font-weight--bold);
line-height: var(--line-height--tight);
letter-spacing: var(--letter-spacing--tight);
}
h3 {
font-family: var(--font-family--display);
font-size: var(--font-size--xl);
font-weight: var(--font-weight--semibold);
line-height: var(--line-height--tight);
}
p {
font-size: var(--font-size--md);
line-height: var(--line-height--relaxed);
}
small {
font-size: var(--font-size--sm);
line-height: var(--line-height--normal);
}
code {
font-family: var(--font-family--mono);
font-size: var(--font-size--sm);
letter-spacing: var(--letter-spacing--wide);
}
.lead {
font-size: var(--font-size--lg);
font-weight: var(--font-weight--medium);
line-height: var(--line-height--relaxed);
}
.label {
font-size: var(--font-size--xs);
font-weight: var(--font-weight--medium);
text-transform: uppercase;
letter-spacing: var(--letter-spacing--wide);
}
Responsive Typography
Adjust typography variables at different breakpoints for optimal readability:
import { styleframe } from 'styleframe';
import { useFontSize, useLineHeight } from '@styleframe/theme';
const s = styleframe();
const { ref, selector, variable, media } = s;
const { fontSize } = useFontSize(s, { default: '1rem' });
const { lineHeight } = useLineHeight(s);
selector('body', {
fontSize: ref(fontSize),
lineHeight: ref(lineHeight),
});
// Increase base font size on larger screens
selector('body', ({ media }) => {
media('(min-width: 768px)', ({ variable }) => {
variable(fontSize, '1.125rem');
});
});
selector('body', ({ media }) => {
media('(min-width: 1200px)', ({ variable }) => {
variable(fontSize, '1.25rem');
});
});
export default s;
:root {
--font-size: 1rem;
--line-height--tight: 1.2;
--line-height--snug: 1.35;
--line-height--normal: 1.5;
--line-height--relaxed: 1.65;
--line-height--loose: 1.9;
--line-height: var(--line-height--normal);
}
body {
font-size: var(--font-size);
line-height: var(--line-height);
}
body {
@media (min-width: 768px) {
--font-size: 1.125rem;
}
}
body {
@media (min-width: 1200px) {
--font-size: 1.25rem;
}
}
Best Practices
- Use modular scales for font sizes: Combine
useFontSize()withuseMultiplier()for mathematically harmonious type scales. - Limit your type scale: Aim for 6-8 font sizes. Too many options lead to inconsistency.
- Match line height to font size: Larger text needs tighter line height (1.2), body text works well at 1.5, and small text may need looser spacing (1.75).
- Use negative letter spacing for large headings: Display text benefits from tighter tracking (e.g.,
-0.025em). - Use positive letter spacing for uppercase or small text: All-caps text and small UI text need extra tracking (e.g.,
0.05emto0.1em) for readability. - Always include font stack fallbacks: List fonts from most specific to most generic, ending with a generic family.
font-family: "Inter", "Segoe UI", Roboto, Helvetica, Arial, sans-serif; - Use
local()in @font-face: Includelocal()sources to reuse installed fonts when available, reducing bandwidth. - Consider performance:
- System fonts load instantly; web fonts may cause layout shifts. Use
font-display: swap. - Preload critical web fonts:
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin> - Use
preconnectfor external font services:<link rel="preconnect" href="https://fonts.googleapis.com"> - Use
font-size-adjustorsize-adjust(in@font-face) to reduce reflow when fallback fonts render.
- System fonts load instantly; web fonts may cause layout shifts. Use
- Use unitless line heights: This ensures line height scales proportionally with font size changes.
- Consider variable fonts: Variable fonts offer multiple weights and styles in a single file, improving performance and enabling optical size adjustments.
- Test across devices: Ensure your typography is readable at all viewport sizes and on different devices.
- Use em units for letter spacing: This ensures tracking scales proportionally with font size.
as const to ensure the object is treated as a constant type. This helps TypeScript infer the return type of the composables and provides better type safety and autocomplete support.FAQ
default key creates a variable without a suffix (like --font-family or --font-size), which is perfect for your primary values. Any other key like mono or lg creates variables with suffixes (like --font-family--mono or --font-size--lg). Use default for your main body text styles.@font-face rule or a font loading service like Google Fonts or Adobe Fonts. The typography composables only create CSS variables—they don't load fonts. Use font-display: swap to prevent invisible text while fonts load.rem for font sizes to respect user font size preferences and maintain consistent scaling across your site. rem is relative to the root element, making it predictable. Use em only when you need font sizes relative to their parent element. Avoid px as it doesn't scale with user preferences.italic uses a true italic font variant if available—a specially designed slanted version with unique letterforms. oblique artificially slants the normal font. Most fonts provide italic variants, making italic the preferred choice for emphasis. Use oblique only when the font lacks a true italic or for specific design effects.