
Brand Designer
- 167 installs
- 14 repo stars
- Updated March 2, 2026
- oakoss/agent-skills
Define brand identity, visual language, and design direction before building marketing surfaces or product UI.
About
Helps agents craft cohesive brand systems early: positioning, color and type choices, visual tone, logo direction, and reusable identity guidelines that inform later UI, marketing, and launch materials.
- Brand identity
- Color palettes
- Typography systems
- Visual tone
- Logo direction
Brand Designer by the numbers
- 167 all-time installs (skills.sh)
- +7 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #983 of 1,880 Design & UI/UX skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/oakoss/agent-skills --skill brand-designerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 167 |
|---|---|
| repo stars | ★ 14 |
| Last updated | March 2, 2026 |
| Repository | oakoss/agent-skills ↗ |
What it does
Define brand identity, visual language, and design direction before building marketing surfaces or product UI.
Files
Brand Designer
Overview
Brand Designer creates complete visual identity systems from discovery through asset delivery. It covers logo design with SVG components, color palette generation with accessibility compliance, typography scale configuration, brand guidelines documentation, and template creation for social media and print collateral.
When to use: New brand identity projects, logo design with SVG variants, color palette creation with Tailwind integration, typography system setup, brand guidelines documentation, social media template generation, brand asset file organization.
When NOT to use: UI component library design (use design-system), image optimization pipelines (use asset-manager), Figma-to-code workflows (use figma-developer), marketing copy or content strategy.
Quick Reference
| Pattern | Approach | Key Points |
|---|---|---|
| Logo design | SVG component with full/icon/wordmark variants | Design monochrome first, then add color |
| Color palette | Primary, secondary, neutral, semantic layers | 60/30/10 rule: neutral/primary/accent |
| Typography system | Display + body font pair with CSS custom props | Maximum two font families |
| Brand guidelines | Logo usage, colors, typography, voice and tone | Include clear space rules and minimum sizes |
| Social templates | SVG templates at platform-specific dimensions | 1200x630 for Open Graph, 400x400 for profiles |
| Asset organization | Structured directories: logo, colors, fonts | SVG as primary format, PNG at @1x/@2x/@3x |
| Favicon generation | Sharp-based script from SVG source | Generate 16, 32, 48, 64, 128, 256px sizes |
| Accessibility | WCAG AA contrast ratios for text on backgrounds | Verify primary color passes AA on white |
Brand Discovery Checklist
| Question | Purpose |
|---|---|
| What does the company do? | Industry context |
| Who is the target audience? | Demographic and psychographic targeting |
| What are the brand values? | Core identity pillars |
| What feeling should the logo evoke? | Emotional direction |
| Any colors or symbols to avoid? | Constraints and sensitivities |
| Who are the competitors? | Differentiation opportunities |
Common Mistakes
| Mistake | Correct Pattern |
|---|---|
| Designing the logo in color first | Design in monochrome first; logo must work in a single color before adding palette |
| Skipping the brand discovery checklist | Always gather company values, audience, competitors, and constraints before design |
| Using more than two font families | Limit to one display font and one body font for cohesion |
| Not providing logo variants | Create full, icon-only, and wordmark-only variants plus monochrome versions |
| Exporting logos only as PNG | Provide SVG as primary format; add PNG at @1x, @2x, @3x for raster needs |
| Using brand color for large backgrounds | Follow 60/30/10 rule: 60% neutral, 30% primary, 10% accent |
| Adding effects to logo (shadows, gradients) | Logo is used as-is from approved variants only |
| Missing clear space rules | Define clear space equal to height of tallest letter around logo |
| No minimum size specification | Digital: 120px full / 40px icon; Print: 1in full / 0.25in icon |
Delegation
When working on brand design, delegate to:
design-system-- Token hierarchy and component architectureasset-manager-- Image optimization and asset pipelinefigma-developer-- Figma-to-code implementation
References
- Logo Design -- SVG logo component with full, icon, and wordmark variants
- Color Palette -- Brand color definitions, Tailwind config, usage guidelines
- Typography -- Font selection, CSS custom properties, React typography components
- Brand Guidelines -- Complete guidelines template with voice, tone, and visual rules
- Templates -- Social media, business card, and profile image SVG templates
- Asset Management -- File organization, favicon generation, HTML meta tags
Asset Management
File Organization
brand-assets/
├── logo/
│ ├── svg/
│ │ ├── logo-full.svg
│ │ ├── logo-icon.svg
│ │ └── logo-wordmark.svg
│ ├── png/
│ │ ├── logo-full@1x.png
│ │ ├── logo-full@2x.png
│ │ └── logo-full@3x.png
│ └── favicon/
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ └── favicon.ico
├── colors/
│ └── palette.json
├── fonts/
│ ├── Inter-Regular.woff2
│ ├── Inter-Bold.woff2
│ └── JetBrainsMono-Regular.woff2
├── templates/
│ ├── social-profile.svg
│ ├── social-post.svg
│ └── business-card.svg
└── guidelines/
└── brand-guidelines.pdfFavicon Generation
import sharp from 'sharp';
async function generateFavicons() {
const sizes = [16, 32, 48, 64, 128, 256];
for (const size of sizes) {
await sharp('logo-icon.svg')
.resize(size, size)
.png()
.toFile(`public/favicon-${size}x${size}.png`);
console.log(`Generated ${size}x${size} favicon`);
}
}
generateFavicons();Favicon HTML
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />Brand Guidelines Template
Logo Usage
Logo Variations
- Full Logo: Use on marketing materials, website header
- Icon Only: Use for app icon, favicon, social media avatars
- Wordmark: Use when icon doesn't fit context
Clear Space
Maintain clear space around logo equal to height of the tallest letter.
Minimum Size
- Digital: 120px width (full logo), 40px (icon)
- Print: 1 inch width (full logo), 0.25 inch (icon)
Logo Don'ts
- Don't rotate the logo
- Don't change colors (except approved variations)
- Don't add effects (shadows, gradients)
- Don't distort or stretch
Color Specifications
Primary Colors
- Brand Blue: #0066CC
- RGB: 0, 102, 204
- CMYK: 100, 50, 0, 20
- Accent Orange: #FF9100
- RGB: 255, 145, 0
- CMYK: 0, 43, 100, 0
Usage
- Primary buttons, links: Brand Blue
- CTAs, highlights: Accent Orange
- Backgrounds: Neutral grays
Typography
Fonts
- Headings: Inter Bold (700)
- Body: Inter Regular (400)
- Code: JetBrains Mono Regular (400)
Hierarchy
- H1: 48px / Bold / Tight leading
- H2: 36px / Semibold / Tight leading
- Body: 16px / Regular / Normal leading
- Caption: 14px / Regular / Normal leading
Voice and Tone
Brand Personality
- Professional but not corporate
- Technical but approachable
- Innovative but reliable
Writing Style
- Use active voice
- Be concise and clear
- Avoid jargon (unless technical docs)
- Use "we" and "you" (not "I" or "one")
Examples
- Good: "Deploy your app in seconds"
- Bad: "Applications can be deployed quickly"
- Good: "We built this for developers like you"
- Bad: "This product was designed for developer users"
Color Palette
Brand Colors
export const brandColors = {
primary: {
50: '#E6F0FF',
100: '#CCE0FF',
200: '#99C2FF',
300: '#66A3FF',
400: '#3385FF',
500: '#0066CC', // Main brand color
600: '#0052A3',
700: '#003D7A',
800: '#002952',
900: '#001429',
},
secondary: {
50: '#FFF4E6',
100: '#FFE9CC',
200: '#FFD399',
300: '#FFBD66',
400: '#FFA733',
500: '#FF9100', // Main accent
600: '#CC7400',
700: '#995700',
800: '#663A00',
900: '#331D00',
},
neutral: {
50: '#F9FAFB',
100: '#F3F4F6',
200: '#E5E7EB',
300: '#D1D5DB',
400: '#9CA3AF',
500: '#6B7280',
600: '#4B5563',
700: '#374151',
800: '#1F2937',
900: '#111827',
},
success: '#10B981',
warning: '#F59E0B',
error: '#EF4444',
info: '#3B82F6',
};Tailwind Configuration
module.exports = {
theme: {
colors: {
primary: brandColors.primary,
secondary: brandColors.secondary,
gray: brandColors.neutral,
green: brandColors.success,
},
},
};Color Usage Guidelines
Primary Blue (#0066CC)
- Use for: Primary buttons, links, active states, brand elements
- Don't use for: Backgrounds, large areas
- Accessibility: Passes WCAG AA for text on white
Secondary Orange (#FF9100)
- Use for: CTAs, highlights, important actions
- Don't use for: Body text
- Pairing: Works best with primary blue
Neutral Grays
- Use for: Text, borders, backgrounds, UI elements
- Hierarchy:
- 900: Headings
- 700: Body text
- 500: Secondary text
- 300: Borders
- 100: Backgrounds
Color Theory Fundamentals
Color Relationships
| Harmony | Description | Use Case |
|---|---|---|
| Complementary | Opposite on the color wheel | High contrast CTAs, alerts |
| Analogous | Adjacent colors (30 degrees apart) | Cohesive, harmonious palettes |
| Triadic | Three colors equally spaced (120 degrees) | Vibrant, balanced designs |
| Split-complementary | Base + two adjacent to its complement | Contrast with less tension |
Applying Harmony to Brand Palettes
Start with the brand primary. Use harmony rules to select secondary and accent colors:
1. Primary: Core brand color chosen from discovery 2. Secondary: Use analogous or split-complementary relationship to primary 3. Accent: Use complementary or triadic for high-contrast moments 4. Neutral: Desaturated version of primary for grays with brand warmth
OKLCH Color Space
OKLCH provides perceptually uniform color manipulation. Unlike HSL, equal lightness values in OKLCH actually appear equally light to the human eye.
OKLCH Components
| Component | Range | Purpose |
|---|---|---|
| L (lightness) | 0-1 | Perceptual brightness |
| C (chroma) | 0-0.4+ | Color intensity/saturation |
| H (hue) | 0-360 | Color angle on wheel |
CSS oklch() Syntax
:root {
--brand-primary: oklch(0.55 0.2 250);
--brand-light: oklch(0.85 0.08 250);
--brand-dark: oklch(0.35 0.15 250);
}Why OKLCH Over HSL
/* HSL: these look different brightness despite same L value */
.hsl-blue {
color: hsl(220 80% 50%);
}
.hsl-yellow {
color: hsl(60 80% 50%);
}
/* OKLCH: same L value = same perceived brightness */
.oklch-blue {
color: oklch(0.6 0.15 250);
}
.oklch-yellow {
color: oklch(0.6 0.15 90);
}Palette Generation with OKLCH
Generate consistent shade scales by varying the lightness channel while keeping chroma and hue stable.
Shade Scale Pattern
function generateShadeScale(
hue: number,
chroma: number,
): Record<string, string> {
return {
50: `oklch(0.97 ${chroma * 0.2} ${hue})`,
100: `oklch(0.93 ${chroma * 0.3} ${hue})`,
200: `oklch(0.85 ${chroma * 0.5} ${hue})`,
300: `oklch(0.75 ${chroma * 0.7} ${hue})`,
400: `oklch(0.65 ${chroma * 0.9} ${hue})`,
500: `oklch(0.55 ${chroma} ${hue})`,
600: `oklch(0.48 ${chroma * 0.95} ${hue})`,
700: `oklch(0.40 ${chroma * 0.85} ${hue})`,
800: `oklch(0.30 ${chroma * 0.7} ${hue})`,
900: `oklch(0.20 ${chroma * 0.5} ${hue})`,
};
}Tailwind with OKLCH
export default {
theme: {
extend: {
colors: {
brand: {
50: 'oklch(0.97 0.04 250)',
100: 'oklch(0.93 0.06 250)',
200: 'oklch(0.85 0.10 250)',
300: 'oklch(0.75 0.14 250)',
400: 'oklch(0.65 0.18 250)',
500: 'oklch(0.55 0.20 250)',
600: 'oklch(0.48 0.19 250)',
700: 'oklch(0.40 0.17 250)',
800: 'oklch(0.30 0.14 250)',
900: 'oklch(0.20 0.10 250)',
},
},
},
},
};Semantic Color Mapping
Map palette colors to semantic roles. Components reference semantic tokens, not raw palette values.
Token Structure
:root {
/* Semantic tokens reference palette values */
--color-action: var(--primary-500);
--color-action-hover: var(--primary-600);
--color-accent: var(--secondary-500);
--color-accent-subtle: var(--secondary-100);
--color-text-primary: var(--neutral-900);
--color-text-secondary: var(--neutral-600);
--color-text-muted: var(--neutral-400);
--color-bg-primary: white;
--color-bg-secondary: var(--neutral-50);
--color-bg-elevated: white;
--color-border: var(--neutral-200);
--color-border-focus: var(--primary-500);
--color-danger: var(--red-500);
--color-warning: var(--amber-500);
--color-success: var(--green-500);
}Semantic Mapping Table
| Semantic Role | Light Mode | Purpose |
|---|---|---|
| Action | Primary 500 | Buttons, links, interactive |
| Action hover | Primary 600 | Hover/pressed states |
| Accent | Secondary 500 | Highlights, badges, decorative |
| Text primary | Neutral 900 | Headings, body text |
| Text secondary | Neutral 600 | Captions, labels |
| Background | White | Page background |
| Surface | Neutral 50 | Cards, sections |
| Border | Neutral 200 | Dividers, input borders |
| Danger | Red 500 | Errors, destructive actions |
| Warning | Amber 500 | Caution states |
| Success | Green 500 | Confirmations, positive feedback |
Dark Mode Color Strategy
Dark mode is not color inversion. It requires intentional adjustments to maintain readability and brand recognition.
Key Principles
1. Reduce saturation: Vivid colors strain eyes on dark backgrounds. Drop chroma by 10-20% 2. Adjust lightness: Swap the scale direction. Light mode 900 text becomes dark mode 100 text 3. Maintain contrast: WCAG 4.5:1 for body text, 3:1 for large text and UI elements 4. Elevate with lightness: Instead of shadows, use lighter surface colors to show elevation
Dark Mode Token Overrides
[data-theme='dark'] {
--color-action: var(--primary-400);
--color-action-hover: var(--primary-300);
--color-accent: var(--secondary-400);
--color-text-primary: var(--neutral-100);
--color-text-secondary: var(--neutral-400);
--color-text-muted: var(--neutral-500);
--color-bg-primary: var(--neutral-900);
--color-bg-secondary: var(--neutral-800);
--color-bg-elevated: var(--neutral-800);
--color-border: var(--neutral-700);
--color-border-focus: var(--primary-400);
}Surface Elevation in Dark Mode
| Elevation | Surface Color | Use Case |
|---|---|---|
| Base | Neutral 900 | Page background |
| Level 1 | Neutral 800 | Cards, sidebars |
| Level 2 | Neutral 750 | Dropdowns, popovers |
| Level 3 | Neutral 700 | Modals, dialogs |
Testing Dark Mode Colors
- Verify all text passes WCAG AA contrast on its background
- Check that primary brand color is still recognizable (not washed out)
- Ensure danger/warning/success colors remain distinguishable from each other
- Test in actual dark environments, not just a dark browser window
Logo Design
SVG Logo Component
interface LogoProps {
variant?: 'full' | 'icon' | 'wordmark'
color?: 'primary' | 'white' | 'black'
size?: number
}
export function Logo({ variant = 'full', color = 'primary', size = 40 }: LogoProps) {
const colors = {
primary: '#0066CC',
white: '#FFFFFF',
black: '#000000'
}
const fillColor = colors[color]
if (variant === 'icon') {
return (
<svg width={size} height={size} viewBox="0 0 40 40" fill="none">
<circle cx="20" cy="20" r="18" fill={fillColor} />
<path
d="M15 20 L25 15 L25 25 Z"
fill="white"
/>
</svg>
)
}
if (variant === 'wordmark') {
return (
<svg width={size * 4} height={size} viewBox="0 0 160 40" fill="none">
<text
x="0"
y="30"
fontFamily="Inter, sans-serif"
fontSize="24"
fontWeight="700"
fill={fillColor}
>
TechStart
</text>
</svg>
)
}
// Full logo (icon + wordmark)
return (
<svg width={size * 5} height={size} viewBox="0 0 200 40" fill="none">
<circle cx="20" cy="20" r="18" fill={fillColor} />
<path d="M15 20 L25 15 L25 25 Z" fill="white" />
<text
x="50"
y="30"
fontFamily="Inter, sans-serif"
fontSize="24"
fontWeight="700"
fill={fillColor}
>
TechStart
</text>
</svg>
)
}Usage
<Logo variant="full" />
<Logo variant="icon" size={32} />
<Logo variant="wordmark" color="white" />Templates
Social Media Profile Image
export function SocialProfileImage() {
return (
<svg width="400" height="400" viewBox="0 0 400 400">
<rect width="400" height="400" fill="#0066CC" />
<circle cx="200" cy="200" r="120" fill="white" />
<path
d="M160 200 L240 160 L240 240 Z"
fill="#0066CC"
/>
</svg>
)
}Social Media Post (1200x630)
interface SocialPostProps {
title: string
description: string
imageUrl?: string
}
export function SocialPost({ title, description, imageUrl }: SocialPostProps) {
return (
<svg width="1200" height="630" viewBox="0 0 1200 630">
<defs>
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#0066CC" />
<stop offset="100%" stopColor="#003D7A" />
</linearGradient>
</defs>
<rect width="1200" height="630" fill="url(#bg)" />
<text
x="60"
y="200"
fontSize="60"
fontWeight="700"
fill="white"
fontFamily="Inter"
>
{title}
</text>
<text
x="60"
y="270"
fontSize="32"
fill="#CCE0FF"
fontFamily="Inter"
>
{description}
</text>
</svg>
)
}Business Card (350x200)
interface BusinessCardProps {
name: string
title: string
email: string
phone: string
}
export function BusinessCard({ name, title, email, phone }: BusinessCardProps) {
return (
<svg width="350" height="200" viewBox="0 0 350 200">
<rect width="350" height="200" fill="white" />
<text x="20" y="120" fontSize="20" fontWeight="700" fill="#111827">
{name}
</text>
<text x="20" y="145" fontSize="14" fill="#6B7280">
{title}
</text>
<text x="20" y="170" fontSize="12" fill="#6B7280">
{email}
</text>
<text x="20" y="185" fontSize="12" fill="#6B7280">
{phone}
</text>
</svg>
)
}Typography
Font Selection
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;600&display=swap');
:root {
--font-sans:
'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--font-mono: 'JetBrains Mono', 'Courier New', monospace;
--text-xs: 0.75rem; /* 12px */
--text-sm: 0.875rem; /* 14px */
--text-base: 1rem; /* 16px */
--text-lg: 1.125rem; /* 18px */
--text-xl: 1.25rem; /* 20px */
--text-2xl: 1.5rem; /* 24px */
--text-3xl: 1.875rem; /* 30px */
--text-4xl: 2.25rem; /* 36px */
--text-5xl: 3rem; /* 48px */
--font-normal: 400;
--font-medium: 500;
--font-semibold: 600;
--font-bold: 700;
--leading-tight: 1.25;
--leading-normal: 1.5;
--leading-relaxed: 1.75;
}Font Pairing Principles
Contrast Creates Hierarchy
Pair fonts with distinct characteristics. The display font draws attention; the body font stays readable at length.
| Pairing Strategy | Display Font | Body Font | Mood |
|---|---|---|---|
| Serif + Sans-serif | Playfair Display | Inter | Elegant, editorial |
| Geometric + Humanist | Poppins | Source Sans 3 | Modern, approachable |
| Slab + Grotesque | Roboto Slab | Roboto | Technical, grounded |
| Monospace + Sans-serif | JetBrains Mono | Inter | Developer, precise |
Pairing Checklist
- Contrast: Fonts should differ in classification (serif vs sans, geometric vs humanist)
- Mood match: Both fonts should support the same brand personality
- X-height alignment: Similar x-heights prevent jarring shifts when fonts appear near each other
- Weight range: Both fonts should offer enough weights for hierarchy (at minimum 400 and 700)
Type Scale Ratios
A type scale creates consistent size relationships. Multiply the base size by the ratio to get the next step.
Common Ratios
| Ratio | Value | Character |
|---|---|---|
| Minor Second | 1.067 | Subtle, tight hierarchy |
| Major Second | 1.125 | Compact UI, dashboards |
| Minor Third | 1.2 | General purpose, balanced |
| Major Third | 1.25 | Clear hierarchy, most versatile |
| Perfect Fourth | 1.333 | Strong contrast, editorial |
| Golden Ratio | 1.618 | Dramatic, hero-heavy layouts |
Calculating a Scale
Formula: size = base * ratio ^ step
function generateTypeScale(
base: number,
ratio: number,
steps: number,
): number[] {
return Array.from(
{ length: steps },
(_, i) => Math.round(base * Math.pow(ratio, i) * 100) / 100,
);
}Major Third scale from 16px base: 16, 20, 25, 31.25, 39.06, 48.83
Responsive Typography with CSS clamp()
clamp() creates fluid typography that scales smoothly between breakpoints without media queries.
Pattern
clamp(minimum, preferred, maximum)- Minimum: smallest readable size (mobile)
- Preferred: fluid value using viewport units
- Maximum: largest size (desktop cap)
Implementation
:root {
--text-base: clamp(1rem, 0.5rem + 1vw, 1.125rem);
--text-lg: clamp(1.125rem, 0.75rem + 1.25vw, 1.5rem);
--text-xl: clamp(1.25rem, 0.5rem + 2vw, 2rem);
--text-2xl: clamp(1.5rem, 0.25rem + 3vw, 2.5rem);
--text-hero: clamp(2rem, 1rem + 4vw, 4rem);
}Tailwind Arbitrary Values
<h1 className="text-[clamp(2rem,1rem+4vw,4rem)] font-bold leading-tight">
Fluid Heading
</h1>Variable Fonts
Variable fonts contain multiple styles in a single file, reducing HTTP requests and enabling smooth weight transitions.
Common Variation Axes
| Axis | CSS Property | Range (typical) | Purpose |
|---|---|---|---|
wght | font-weight | 100-900 | Weight |
wdth | font-stretch | 75%-125% | Width |
opsz | font-optical-sizing | 8-144 | Optical size |
ital | font-style | 0-1 | Italic |
Loading a Variable Font
@font-face {
font-family: 'Inter';
src: url('/fonts/Inter-Variable.woff2') format('woff2-variations');
font-weight: 100 900;
font-display: swap;
}Animated Weight Transition
.nav-link {
font-variation-settings: 'wght' 400;
transition: font-variation-settings 200ms ease-out;
}
.nav-link:hover {
font-variation-settings: 'wght' 600;
}Performance Benefits
- Single file replaces 4-8 static font files
- Smaller total download (one variable font < multiple static weights)
- Enables fine-grained weight control (e.g., 450 instead of snapping to 400 or 500)
Vertical Rhythm
Consistent vertical spacing tied to the line-height creates visual harmony.
Base Unit
Derive a spacing unit from the body line-height. With a 16px base and 1.5 line-height, the rhythm unit is 24px.
:root {
--rhythm: 1.5rem; /* 24px at 16px base */
}
h1 {
margin-bottom: var(--rhythm);
}
h2 {
margin-bottom: calc(var(--rhythm) * 0.75);
}
p {
margin-bottom: var(--rhythm);
}Line-Height by Size
Larger text needs tighter line-height. Smaller text needs more breathing room.
| Text Size | Line-Height | Ratio |
|---|---|---|
| 48px+ | 1.1-1.2 | Tight |
| 24-36px | 1.2-1.3 | Snug |
| 16-20px | 1.5 | Normal |
| 12-14px | 1.6-1.75 | Relaxed |
Spacing Relationships
:root {
--space-xs: calc(var(--rhythm) * 0.25); /* 6px */
--space-sm: calc(var(--rhythm) * 0.5); /* 12px */
--space-md: var(--rhythm); /* 24px */
--space-lg: calc(var(--rhythm) * 2); /* 48px */
--space-xl: calc(var(--rhythm) * 3); /* 72px */
}Web Font Loading Strategy
font-display Values
| Value | Behavior | Best For |
|---|---|---|
swap | Shows fallback immediately, swaps when ready | Body text, critical content |
optional | Shows fallback, may skip font if too slow | Non-critical display text |
fallback | Brief blank period, then fallback | Balance of speed and polish |
Preload Critical Fonts
<link
rel="preload"
href="/fonts/Inter-Variable.woff2"
as="font"
type="font/woff2"
crossorigin
/>Preload only the primary body font. Let secondary fonts load normally to avoid contention.
WOFF2 Format
Always serve WOFF2. It provides 30% better compression than WOFF and has universal browser support. No need to include WOFF fallbacks for modern applications.
Self-Hosting vs CDN
| Approach | Pros | Cons |
|---|---|---|
| Self-hosted | No third-party requests, full control | Must manage font files |
| Google Fonts | Easy setup, global CDN | Third-party dependency, GDPR |
Self-hosting is preferred for production brands. Eliminates third-party DNS lookups and avoids privacy concerns with Google Fonts tracking.
React Typography Components
export function Heading1({ children }: { children: React.ReactNode }) {
return (
<h1 className="text-4xl font-bold leading-tight text-gray-900">
{children}
</h1>
)
}
export function Heading2({ children }: { children: React.ReactNode }) {
return (
<h2 className="text-3xl font-semibold leading-tight text-gray-900">
{children}
</h2>
)
}
export function BodyText({ children }: { children: React.ReactNode }) {
return (
<p className="text-base font-normal leading-normal text-gray-700">
{children}
</p>
)
}
export function Caption({ children }: { children: React.ReactNode }) {
return (
<p className="text-sm font-normal leading-normal text-gray-500">
{children}
</p>
)
}