@su-record/vibe 2.8.52 → 2.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +37 -37
- package/CLAUDE.md +169 -169
- package/LICENSE +21 -21
- package/README.ko.md +43 -128
- package/README.md +43 -128
- package/agents/architect-low.md +41 -41
- package/agents/architect-medium.md +59 -59
- package/agents/architect.md +80 -80
- package/agents/build-error-resolver.md +115 -115
- package/agents/compounder.md +261 -261
- package/agents/diagrammer.md +178 -178
- package/agents/docs/api-documenter.md +99 -99
- package/agents/docs/changelog-writer.md +93 -93
- package/agents/e2e-tester.md +294 -294
- package/agents/event/event-comms.md +78 -78
- package/agents/event/event-content.md +68 -68
- package/agents/event/event-image.md +95 -95
- package/agents/event/event-ops.md +84 -84
- package/agents/event/event-scheduler.md +69 -69
- package/agents/event/event-speaker.md +86 -86
- package/agents/explorer-low.md +42 -42
- package/agents/explorer-medium.md +59 -59
- package/agents/explorer.md +48 -48
- package/agents/implementer-low.md +43 -43
- package/agents/implementer-medium.md +52 -52
- package/agents/implementer.md +54 -54
- package/agents/junior-mentor.md +141 -141
- package/agents/planning/requirements-analyst.md +84 -84
- package/agents/planning/ux-advisor.md +83 -83
- package/agents/qa/acceptance-tester.md +86 -86
- package/agents/qa/edge-case-finder.md +93 -93
- package/agents/qa/qa-coordinator.md +131 -131
- package/agents/refactor-cleaner.md +143 -143
- package/agents/research/best-practices-agent.md +199 -199
- package/agents/research/codebase-patterns-agent.md +157 -157
- package/agents/research/framework-docs-agent.md +188 -188
- package/agents/research/security-advisory-agent.md +213 -213
- package/agents/review/architecture-reviewer.md +107 -107
- package/agents/review/complexity-reviewer.md +116 -116
- package/agents/review/data-integrity-reviewer.md +88 -88
- package/agents/review/git-history-reviewer.md +103 -103
- package/agents/review/performance-reviewer.md +86 -86
- package/agents/review/python-reviewer.md +150 -150
- package/agents/review/rails-reviewer.md +139 -139
- package/agents/review/react-reviewer.md +144 -144
- package/agents/review/security-reviewer.md +80 -80
- package/agents/review/simplicity-reviewer.md +140 -140
- package/agents/review/test-coverage-reviewer.md +116 -116
- package/agents/review/typescript-reviewer.md +127 -127
- package/agents/searcher.md +54 -54
- package/agents/simplifier.md +120 -120
- package/agents/tester.md +49 -49
- package/agents/ui/ui-a11y-auditor.md +93 -93
- package/agents/ui/ui-antipattern-detector.md +102 -102
- package/agents/ui/ui-dataviz-advisor.md +69 -69
- package/agents/ui/ui-design-system-gen.md +57 -57
- package/agents/ui/ui-industry-analyzer.md +49 -49
- package/agents/ui/ui-layout-architect.md +65 -65
- package/agents/ui/ui-stack-implementer.md +68 -68
- package/agents/ui/ux-compliance-reviewer.md +81 -81
- package/agents/ui-previewer.md +258 -258
- package/commands/vibe.analyze.md +379 -379
- package/commands/vibe.docs.md +32 -32
- package/commands/vibe.event.md +163 -163
- package/commands/vibe.figma.md +69 -69
- package/commands/vibe.review.md +686 -686
- package/commands/vibe.run.md +2276 -2276
- package/commands/vibe.spec.md +1195 -1195
- package/commands/vibe.spec.review.md +609 -609
- package/commands/vibe.trace.md +259 -259
- package/commands/vibe.utils.md +413 -413
- package/commands/vibe.verify.md +510 -510
- package/dist/cli/collaborator.js +52 -52
- package/dist/cli/commands/config.js +9 -9
- package/dist/cli/commands/evolution.js +12 -12
- package/dist/cli/commands/figma.js +20 -20
- package/dist/cli/commands/info.js +53 -53
- package/dist/cli/commands/init.js +5 -5
- package/dist/cli/commands/remove.js +14 -14
- package/dist/cli/commands/sentinel.js +27 -27
- package/dist/cli/commands/skills.js +5 -5
- package/dist/cli/commands/slack.js +10 -10
- package/dist/cli/commands/stats.js +6 -6
- package/dist/cli/commands/telegram.js +12 -12
- package/dist/cli/detect.js +32 -32
- package/dist/cli/index.js +51 -51
- package/dist/cli/llm/claude-commands.js +16 -16
- package/dist/cli/llm/config.js +18 -18
- package/dist/cli/llm/gemini-commands.js +16 -16
- package/dist/cli/llm/gpt-commands.js +19 -19
- package/dist/cli/llm/help.js +21 -21
- package/dist/cli/postinstall/cursor-agents.js +32 -32
- package/dist/cli/postinstall/cursor-rules.js +83 -83
- package/dist/cli/postinstall/cursor-skills.js +743 -743
- package/dist/cli/postinstall/main.d.ts.map +1 -1
- package/dist/cli/postinstall/main.js +40 -66
- package/dist/cli/postinstall/main.js.map +1 -1
- package/dist/cli/setup/Provisioner.js +42 -42
- package/dist/infra/lib/CostAccumulator.d.ts +58 -0
- package/dist/infra/lib/CostAccumulator.d.ts.map +1 -0
- package/dist/infra/lib/CostAccumulator.js +131 -0
- package/dist/infra/lib/CostAccumulator.js.map +1 -0
- package/dist/infra/lib/DeepInit.js +24 -24
- package/dist/infra/lib/IterationTracker.js +11 -11
- package/dist/infra/lib/PythonParser.js +108 -108
- package/dist/infra/lib/ReviewRace.js +96 -96
- package/dist/infra/lib/SkillFrontmatter.js +28 -28
- package/dist/infra/lib/SkillQualityGate.js +9 -9
- package/dist/infra/lib/SkillRepository.js +159 -159
- package/dist/infra/lib/TokenBudgetTracker.d.ts +13 -0
- package/dist/infra/lib/TokenBudgetTracker.d.ts.map +1 -1
- package/dist/infra/lib/TokenBudgetTracker.js +44 -3
- package/dist/infra/lib/TokenBudgetTracker.js.map +1 -1
- package/dist/infra/lib/UltraQA.js +99 -99
- package/dist/infra/lib/autonomy/AuditStore.js +41 -41
- package/dist/infra/lib/autonomy/ConfirmationStore.js +30 -30
- package/dist/infra/lib/autonomy/EventOutbox.js +38 -38
- package/dist/infra/lib/autonomy/PolicyEngine.d.ts +3 -3
- package/dist/infra/lib/autonomy/PolicyEngine.js +18 -18
- package/dist/infra/lib/autonomy/SecuritySentinel.js +1 -1
- package/dist/infra/lib/autonomy/SuggestionStore.js +33 -33
- package/dist/infra/lib/embedding/VectorStore.js +22 -22
- package/dist/infra/lib/evolution/AgentAnalyzer.js +10 -10
- package/dist/infra/lib/evolution/DescriptionOptimizer.js +21 -21
- package/dist/infra/lib/evolution/GenerationRegistry.js +36 -36
- package/dist/infra/lib/evolution/InsightStore.js +90 -90
- package/dist/infra/lib/evolution/ParityTester.js +57 -57
- package/dist/infra/lib/evolution/RollbackManager.js +5 -5
- package/dist/infra/lib/evolution/SkillBenchmark.js +23 -23
- package/dist/infra/lib/evolution/SkillEvalRunner.js +50 -50
- package/dist/infra/lib/evolution/SkillGapDetector.js +10 -10
- package/dist/infra/lib/evolution/UsageTracker.js +28 -28
- package/dist/infra/lib/gemini/orchestration.js +5 -5
- package/dist/infra/lib/gpt/orchestration.js +4 -4
- package/dist/infra/lib/memory/KnowledgeGraph.js +4 -4
- package/dist/infra/lib/memory/MemorySearch.js +57 -57
- package/dist/infra/lib/memory/MemoryStorage.js +181 -181
- package/dist/infra/lib/memory/ObservationStore.js +28 -28
- package/dist/infra/lib/memory/ReflectionStore.js +30 -30
- package/dist/infra/lib/memory/SessionRAGRetriever.js +7 -7
- package/dist/infra/lib/memory/SessionRAGStore.js +225 -225
- package/dist/infra/lib/memory/SessionSummarizer.js +9 -9
- package/dist/infra/orchestrator/AgentManager.js +12 -12
- package/dist/infra/orchestrator/AgentRegistry.js +65 -65
- package/dist/infra/orchestrator/MultiLlmResearch.js +8 -8
- package/dist/infra/orchestrator/SwarmOrchestrator.test.js +16 -16
- package/dist/infra/orchestrator/parallelResearch.js +24 -24
- package/dist/tools/convention/analyzeComplexity.test.js +115 -115
- package/dist/tools/convention/validateCodeQuality.test.js +104 -104
- package/dist/tools/memory/createMemoryTimeline.js +10 -10
- package/dist/tools/memory/getMemoryGraph.js +12 -12
- package/dist/tools/memory/getSessionContext.js +9 -9
- package/dist/tools/memory/linkMemories.js +14 -14
- package/dist/tools/memory/listMemories.js +4 -4
- package/dist/tools/memory/recallMemory.js +4 -4
- package/dist/tools/memory/saveMemory.js +4 -4
- package/dist/tools/memory/searchMemoriesAdvanced.js +23 -23
- package/dist/tools/semantic/analyzeDependencyGraph.js +12 -12
- package/dist/tools/semantic/astGrep.test.js +6 -6
- package/dist/tools/spec/prdParser.test.js +171 -171
- package/dist/tools/spec/specGenerator.js +169 -169
- package/dist/tools/spec/traceabilityMatrix.js +64 -64
- package/dist/tools/spec/traceabilityMatrix.test.js +28 -28
- package/hooks/gemini-hooks.json +73 -73
- package/hooks/hooks.json +174 -174
- package/hooks/scripts/__tests__/keyword-detector.test.js +199 -199
- package/hooks/scripts/__tests__/pre-tool-guard.test.js +286 -286
- package/hooks/scripts/__tests__/sentinel-guard.test.js +210 -210
- package/hooks/scripts/auto-commit.js +97 -97
- package/hooks/scripts/auto-format.js +64 -64
- package/hooks/scripts/auto-test.js +81 -81
- package/hooks/scripts/code-check.js +268 -268
- package/hooks/scripts/codex-detect.js +46 -46
- package/hooks/scripts/codex-review-gate.js +80 -80
- package/hooks/scripts/command-log.js +32 -32
- package/hooks/scripts/context-save.js +353 -353
- package/hooks/scripts/evolution-engine.js +91 -91
- package/hooks/scripts/figma-extract.js +635 -477
- package/hooks/scripts/hud-status.js +321 -321
- package/hooks/scripts/keyword-detector.js +214 -214
- package/hooks/scripts/llm-orchestrate.js +572 -572
- package/hooks/scripts/post-edit.js +32 -32
- package/hooks/scripts/pr-test-gate.js +52 -52
- package/hooks/scripts/pre-tool-guard.js +214 -159
- package/hooks/scripts/prompt-dispatcher.js +185 -185
- package/hooks/scripts/sentinel-guard.js +131 -131
- package/hooks/scripts/session-start.js +177 -177
- package/hooks/scripts/skill-injector.js +83 -83
- package/hooks/scripts/stop-notify.js +209 -209
- package/hooks/scripts/utils.js +243 -243
- package/languages/csharp-unity.md +515 -515
- package/languages/gdscript-godot.md +470 -470
- package/languages/ruby-rails.md +489 -489
- package/languages/typescript-angular.md +433 -433
- package/languages/typescript-astro.md +416 -416
- package/languages/typescript-electron.md +406 -406
- package/languages/typescript-nestjs.md +524 -524
- package/languages/typescript-svelte.md +407 -407
- package/languages/typescript-tauri.md +365 -365
- package/package.json +101 -101
- package/skills/agents-md/SKILL.md +121 -121
- package/skills/agents-md/rubrics/what-to-keep.md +49 -49
- package/skills/agents-md/templates/agents-md.md +36 -36
- package/skills/arch-guard/SKILL.md +181 -181
- package/skills/arch-guard/agents/detector.md +48 -48
- package/skills/arch-guard/agents/reporter.md +48 -48
- package/skills/arch-guard/agents/rule-generator.md +49 -49
- package/skills/arch-guard/agents/violation-checker.md +51 -51
- package/skills/arch-guard/frameworks/clean-architecture.md +108 -108
- package/skills/arch-guard/frameworks/solid.md +102 -102
- package/skills/arch-guard/scripts/check-boundaries.js +90 -90
- package/skills/arch-guard/templates/arch-rules.json +47 -47
- package/skills/arch-guard/templates/violation-report.md +53 -53
- package/skills/brand-assets/SKILL.md +147 -147
- package/skills/brand-assets/rubrics/asset-checklist.md +98 -98
- package/skills/brand-assets/templates/brand-guide.md +161 -161
- package/skills/capability-loop/SKILL.md +168 -168
- package/skills/capability-loop/agents/capability-designer.md +61 -61
- package/skills/capability-loop/agents/failure-analyst.md +55 -55
- package/skills/capability-loop/agents/implementer.md +50 -50
- package/skills/capability-loop/agents/tester.md +53 -53
- package/skills/capability-loop/templates/capability-spec.md +118 -118
- package/skills/capability-loop/templates/failure-analysis.md +118 -118
- package/skills/characterization-test/SKILL.md +207 -207
- package/skills/characterization-test/agents/behavior-capturer.md +50 -50
- package/skills/characterization-test/agents/coverage-checker.md +54 -54
- package/skills/characterization-test/agents/reporter.md +50 -50
- package/skills/characterization-test/agents/test-writer.md +49 -49
- package/skills/characterization-test/rubrics/coverage-criteria.md +53 -53
- package/skills/characterization-test/templates/test-template.ts +101 -101
- package/skills/chub-usage/SKILL.md +139 -139
- package/skills/claude-md-guide/SKILL.md +351 -351
- package/skills/claude-md-guide/rubrics/anti-patterns.md +88 -88
- package/skills/claude-md-guide/templates/claude-md.md +54 -54
- package/skills/commerce-patterns/SKILL.md +64 -64
- package/skills/commerce-patterns/rubrics/checkout-flow.md +48 -48
- package/skills/commerce-patterns/templates/product-schema.md +85 -85
- package/skills/commit-push-pr/SKILL.md +77 -77
- package/skills/commit-push-pr/agents/change-analyzer.md +55 -55
- package/skills/commit-push-pr/agents/message-writer.md +50 -50
- package/skills/commit-push-pr/agents/pr-writer.md +58 -58
- package/skills/commit-push-pr/agents/reviewer.md +52 -52
- package/skills/commit-push-pr/rubrics/commit-message.md +73 -73
- package/skills/commit-push-pr/templates/pr-body.md +63 -63
- package/skills/context7-usage/SKILL.md +106 -106
- package/skills/context7-usage/rubrics/when-to-use.md +50 -50
- package/skills/create-prd/SKILL.md +90 -90
- package/skills/create-prd/agents/edge-case-finder.md +48 -48
- package/skills/create-prd/agents/prioritizer.md +60 -60
- package/skills/create-prd/agents/requirements-writer.md +48 -48
- package/skills/create-prd/agents/researcher.md +55 -55
- package/skills/create-prd/agents/reviewer.md +54 -54
- package/skills/create-prd/frameworks/jobs-to-be-done.md +96 -96
- package/skills/create-prd/frameworks/rice-scoring.md +97 -97
- package/skills/create-prd/orchestrator.md +70 -70
- package/skills/create-prd/rubrics/completeness.md +58 -58
- package/skills/create-prd/templates/prd.md +139 -139
- package/skills/design-audit/SKILL.md +152 -152
- package/skills/design-audit/agents/a11y-auditor.md +43 -43
- package/skills/design-audit/agents/performance-auditor.md +46 -46
- package/skills/design-audit/agents/responsive-auditor.md +46 -46
- package/skills/design-audit/agents/scorer.md +47 -47
- package/skills/design-audit/agents/slop-detector.md +47 -47
- package/skills/design-audit/frameworks/core-web-vitals.md +107 -107
- package/skills/design-audit/frameworks/wcag-checklist.md +64 -64
- package/skills/design-audit/orchestrator.md +64 -64
- package/skills/design-audit/rubrics/ai-slop-patterns.md +83 -83
- package/skills/design-audit/rubrics/scoring.md +63 -63
- package/skills/design-audit/templates/report.md +88 -88
- package/skills/design-critique/SKILL.md +139 -139
- package/skills/design-critique/rubrics/ux-heuristics.md +143 -143
- package/skills/design-critique/templates/critique-report.md +86 -86
- package/skills/design-distill/SKILL.md +130 -130
- package/skills/design-distill/templates/design-system.md +132 -132
- package/skills/design-normalize/SKILL.md +133 -133
- package/skills/design-normalize/rubrics/token-naming.md +117 -117
- package/skills/design-normalize/templates/token-audit.md +89 -89
- package/skills/design-polish/SKILL.md +131 -131
- package/skills/design-polish/rubrics/polish-checklist.md +68 -68
- package/skills/design-polish/templates/polish-report.md +64 -64
- package/skills/design-teach/SKILL.md +182 -182
- package/skills/design-teach/rubrics/brand-personality.md +73 -73
- package/skills/design-teach/templates/design-context.json +36 -36
- package/skills/e2e-commerce/SKILL.md +62 -62
- package/skills/e2e-commerce/templates/test-scenarios.md +170 -170
- package/skills/event-comms/SKILL.md +162 -162
- package/skills/event-comms/templates/email-invite.md +99 -99
- package/skills/event-comms/templates/sns-post.md +133 -133
- package/skills/event-ops/SKILL.md +198 -198
- package/skills/event-ops/rubrics/contingency.md +85 -85
- package/skills/event-ops/templates/d-day-checklist.md +65 -65
- package/skills/event-planning/SKILL.md +132 -132
- package/skills/event-planning/rubrics/timeline.md +70 -70
- package/skills/event-planning/templates/event-plan.md +91 -91
- package/skills/exec-plan/SKILL.md +149 -149
- package/skills/exec-plan/agents/decomposer.md +47 -47
- package/skills/exec-plan/agents/dependency-mapper.md +44 -44
- package/skills/exec-plan/agents/estimator.md +43 -43
- package/skills/exec-plan/agents/validator.md +55 -55
- package/skills/exec-plan/orchestrator.md +70 -70
- package/skills/exec-plan/rubrics/complexity-scoring.md +75 -75
- package/skills/exec-plan/templates/plan.md +147 -147
- package/skills/git-worktree/SKILL.md +73 -73
- package/skills/git-worktree/rubrics/when-to-use.md +55 -55
- package/skills/handoff/SKILL.md +110 -110
- package/skills/handoff/agents/context-summarizer.md +51 -51
- package/skills/handoff/agents/document-writer.md +63 -63
- package/skills/handoff/agents/state-collector.md +53 -53
- package/skills/handoff/agents/verifier.md +48 -48
- package/skills/handoff/rubrics/completeness.md +62 -62
- package/skills/handoff/templates/handoff.md +107 -107
- package/skills/parallel-research/SKILL.md +89 -89
- package/skills/parallel-research/agents/best-practices.md +43 -43
- package/skills/parallel-research/agents/codebase-patterns.md +46 -46
- package/skills/parallel-research/agents/framework-docs.md +45 -45
- package/skills/parallel-research/agents/security-advisory.md +46 -46
- package/skills/parallel-research/agents/synthesizer.md +52 -52
- package/skills/parallel-research/experts/best-practices.md +50 -50
- package/skills/parallel-research/experts/codebase-patterns.md +70 -70
- package/skills/parallel-research/experts/framework-docs.md +65 -65
- package/skills/parallel-research/experts/security-advisory.md +69 -69
- package/skills/parallel-research/orchestrator.md +65 -65
- package/skills/parallel-research/templates/synthesis.md +101 -101
- package/skills/prioritization-frameworks/SKILL.md +87 -87
- package/skills/prioritization-frameworks/rubrics/frameworks.md +79 -79
- package/skills/prioritization-frameworks/templates/scoring-matrix.md +69 -69
- package/skills/priority-todos/SKILL.md +64 -64
- package/skills/priority-todos/rubrics/prioritization.md +70 -70
- package/skills/priority-todos/templates/todo-board.md +59 -59
- package/skills/seo-checklist/SKILL.md +58 -58
- package/skills/seo-checklist/frameworks/structured-data.md +153 -153
- package/skills/seo-checklist/rubrics/content-seo.md +42 -42
- package/skills/seo-checklist/rubrics/technical-seo.md +48 -48
- package/skills/techdebt/SKILL.md +124 -124
- package/skills/techdebt/agents/analyzer.md +50 -50
- package/skills/techdebt/agents/fixer.md +41 -41
- package/skills/techdebt/agents/reviewer.md +47 -47
- package/skills/techdebt/agents/scanner.md +44 -44
- package/skills/techdebt/orchestrator.md +70 -70
- package/skills/techdebt/rubrics/severity.md +51 -51
- package/skills/techdebt/scripts/scan.js +90 -90
- package/skills/techdebt/templates/report.md +86 -86
- package/skills/tool-fallback/SKILL.md +104 -104
- package/skills/tool-fallback/rubrics/fallback-chain.md +58 -58
- package/skills/typescript-advanced-types/SKILL.md +67 -67
- package/skills/typescript-advanced-types/rubrics/type-patterns.md +109 -109
- package/skills/ui-ux-pro-max/SKILL.md +236 -236
- package/skills/ui-ux-pro-max/reference/color-and-contrast.md +517 -517
- package/skills/ui-ux-pro-max/reference/interaction-design.md +544 -544
- package/skills/ui-ux-pro-max/reference/motion-design.md +591 -591
- package/skills/ui-ux-pro-max/reference/responsive-design.md +463 -463
- package/skills/ui-ux-pro-max/reference/spatial-design.md +390 -390
- package/skills/ui-ux-pro-max/reference/typography.md +455 -455
- package/skills/ui-ux-pro-max/reference/ux-writing.md +469 -469
- package/skills/ui-ux-pro-max/rubrics/interaction-states.md +83 -83
- package/skills/ui-ux-pro-max/rubrics/responsive-breakpoints.md +99 -99
- package/skills/user-personas/SKILL.md +75 -75
- package/skills/user-personas/rubrics/research-methods.md +56 -56
- package/skills/user-personas/templates/persona.md +89 -89
- package/skills/vercel-react-best-practices/SKILL.md +60 -60
- package/skills/vercel-react-best-practices/rubrics/performance.md +82 -82
- package/skills/vercel-react-best-practices/rubrics/server-components.md +86 -86
- package/skills/vibe.docs/SKILL.md +171 -171
- package/skills/vibe.docs/templates/architecture.md +80 -80
- package/skills/vibe.docs/templates/readme.md +84 -84
- package/skills/vibe.docs/templates/release-notes.md +74 -74
- package/skills/vibe.figma/SKILL.md +215 -982
- package/skills/vibe.figma/rubrics/extraction-checklist.md +51 -51
- package/skills/vibe.figma/templates/component-index.md +126 -126
- package/skills/vibe.figma/templates/figma-handoff.md +100 -100
- package/skills/vibe.figma/templates/remapped-tree.md +277 -277
- package/skills/vibe.figma.convert/SKILL.md +188 -511
- package/skills/vibe.figma.convert/rubrics/conversion-rules.md +129 -113
- package/skills/vibe.figma.convert/templates/component.md +140 -140
- package/skills/vibe.figma.extract/SKILL.md +179 -300
- package/skills/vibe.figma.extract/rubrics/image-rules.md +145 -137
- package/skills/video-production/SKILL.md +52 -52
- package/skills/video-production/rubrics/quality-checklist.md +58 -58
- package/skills/video-production/templates/production-plan.md +104 -104
- package/vibe/config.json +29 -29
- package/vibe/constitution.md +227 -227
- package/vibe/rules/principles/communication-guide.md +98 -98
- package/vibe/rules/principles/development-philosophy.md +52 -52
- package/vibe/rules/principles/quick-start.md +102 -102
- package/vibe/rules/quality/bdd-contract-testing.md +393 -393
- package/vibe/rules/quality/checklist.md +276 -276
- package/vibe/rules/quality/performance.md +236 -236
- package/vibe/rules/quality/testing-strategy.md +440 -440
- package/vibe/rules/standards/anti-patterns.md +541 -541
- package/vibe/rules/standards/code-structure.md +291 -291
- package/vibe/rules/standards/complexity-metrics.md +313 -313
- package/vibe/rules/standards/git-workflow.md +237 -237
- package/vibe/rules/standards/naming-conventions.md +198 -198
- package/vibe/rules/standards/security.md +305 -305
- package/vibe/rules/writing/document-style.md +74 -74
- package/vibe/setup.sh +31 -31
- package/vibe/templates/constitution-template.md +252 -252
- package/vibe/templates/contract-backend-template.md +526 -526
- package/vibe/templates/contract-frontend-template.md +599 -599
- package/vibe/templates/feature-template.md +96 -96
- package/vibe/templates/spec-template.md +221 -221
- package/vibe/ui-ux-data/charts.csv +26 -26
- package/vibe/ui-ux-data/colors.csv +97 -97
- package/vibe/ui-ux-data/icons.csv +101 -101
- package/vibe/ui-ux-data/landing.csv +31 -31
- package/vibe/ui-ux-data/products.csv +96 -96
- package/vibe/ui-ux-data/react-performance.csv +45 -45
- package/vibe/ui-ux-data/stacks/astro.csv +54 -54
- package/vibe/ui-ux-data/stacks/flutter.csv +53 -53
- package/vibe/ui-ux-data/stacks/html-tailwind.csv +56 -56
- package/vibe/ui-ux-data/stacks/jetpack-compose.csv +53 -53
- package/vibe/ui-ux-data/stacks/nextjs.csv +53 -53
- package/vibe/ui-ux-data/stacks/nuxt-ui.csv +51 -51
- package/vibe/ui-ux-data/stacks/nuxtjs.csv +59 -59
- package/vibe/ui-ux-data/stacks/react-native.csv +52 -52
- package/vibe/ui-ux-data/stacks/react.csv +54 -54
- package/vibe/ui-ux-data/stacks/shadcn.csv +61 -61
- package/vibe/ui-ux-data/stacks/svelte.csv +54 -54
- package/vibe/ui-ux-data/stacks/swiftui.csv +51 -51
- package/vibe/ui-ux-data/stacks/vue.csv +50 -50
- package/vibe/ui-ux-data/styles.csv +68 -68
- package/vibe/ui-ux-data/typography.csv +57 -57
- package/vibe/ui-ux-data/ui-reasoning.csv +101 -101
- package/vibe/ui-ux-data/ux-guidelines.csv +99 -99
- package/vibe/ui-ux-data/version.json +31 -31
- package/vibe/ui-ux-data/web-interface.csv +31 -31
|
@@ -1,455 +1,455 @@
|
|
|
1
|
-
# Typography Systems for Web UI
|
|
2
|
-
|
|
3
|
-
A deep reference for building consistent, accessible, and visually compelling typography in production web interfaces.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Modular Scale
|
|
8
|
-
|
|
9
|
-
A modular scale establishes a mathematical relationship between type sizes, making every heading and body size feel intentional rather than arbitrary. The scale is driven by a **base size** (typically 16px) and a **ratio** that multiplies or divides to produce each step.
|
|
10
|
-
|
|
11
|
-
Common ratios:
|
|
12
|
-
- **1.25** (Major Third) — subtle, compact; good for dense UIs and dashboards
|
|
13
|
-
- **1.333** (Perfect Fourth) — balanced; versatile across marketing and application UIs
|
|
14
|
-
- **1.414** (Augmented Fourth / √2) — noticeable contrast; suits editorial layouts
|
|
15
|
-
- **1.5** (Perfect Fifth) — dramatic; use for landing pages, not data-heavy apps
|
|
16
|
-
|
|
17
|
-
**Limit yourself to five sizes or fewer.** Each size must serve a clear semantic role:
|
|
18
|
-
|
|
19
|
-
| Token | Role | 1.25 ratio | 1.333 ratio |
|
|
20
|
-
|-------------|-------------------|------------|-------------|
|
|
21
|
-
| `display` | Hero headlines | ~39px | ~48px |
|
|
22
|
-
| `h1` | Page title | ~31px | ~36px |
|
|
23
|
-
| `h2` | Section heading | ~25px | ~27px |
|
|
24
|
-
| `body` | Reading text | 16px | 16px |
|
|
25
|
-
| `small` | Captions, labels | ~13px | ~12px |
|
|
26
|
-
|
|
27
|
-
**Tailwind CSS approach** using CSS custom properties:
|
|
28
|
-
|
|
29
|
-
```css
|
|
30
|
-
/* globals.css */
|
|
31
|
-
:root {
|
|
32
|
-
--text-display: clamp(2rem, 5vw, 2.441rem);
|
|
33
|
-
--text-h1: clamp(1.75rem, 4vw, 1.953rem);
|
|
34
|
-
--text-h2: clamp(1.375rem, 3vw, 1.563rem);
|
|
35
|
-
--text-body: 1rem;
|
|
36
|
-
--text-small: 0.8rem;
|
|
37
|
-
}
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
```js
|
|
41
|
-
// tailwind.config.js
|
|
42
|
-
module.exports = {
|
|
43
|
-
theme: {
|
|
44
|
-
extend: {
|
|
45
|
-
fontSize: {
|
|
46
|
-
display: 'var(--text-display)',
|
|
47
|
-
h1: 'var(--text-h1)',
|
|
48
|
-
h2: 'var(--text-h2)',
|
|
49
|
-
body: 'var(--text-body)',
|
|
50
|
-
small: 'var(--text-small)',
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
}
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
**DO:** Pick one ratio and apply it globally. Consistency builds visual rhythm.
|
|
58
|
-
|
|
59
|
-
**DON'T:** Mix ratios or hand-pick arbitrary pixel values (e.g., 14, 17, 22, 28, 38). This creates visual noise that readers feel even when they cannot name it.
|
|
60
|
-
|
|
61
|
-
---
|
|
62
|
-
|
|
63
|
-
## Line Height
|
|
64
|
-
|
|
65
|
-
Line height (leading) controls the breathing room between lines of text. Too tight and lines collide; too loose and the text reads as disconnected fragments.
|
|
66
|
-
|
|
67
|
-
**Recommended values:**
|
|
68
|
-
|
|
69
|
-
| Context | Line Height Range | Reasoning |
|
|
70
|
-
|-----------------|-------------------|----------------------------------------------------|
|
|
71
|
-
| Body text | 1.5 – 1.75 | Sustained reading needs air; 1.6 is a safe default |
|
|
72
|
-
| Headings | 1.1 – 1.3 | Short lines at large sizes; tighter leading feels intentional |
|
|
73
|
-
| Captions/labels | 1.3 – 1.5 | Short, single-line; slightly relaxed helps scanning |
|
|
74
|
-
|
|
75
|
-
The reason headings use tighter leading: at large sizes the optical gap between lines grows significantly. A display heading at 40px with `line-height: 1.5` produces 20px of space — far too much for two or three words.
|
|
76
|
-
|
|
77
|
-
**React + Tailwind implementation:**
|
|
78
|
-
|
|
79
|
-
```tsx
|
|
80
|
-
// Typography.tsx
|
|
81
|
-
const styles = {
|
|
82
|
-
display: 'text-display leading-[1.1] font-bold',
|
|
83
|
-
h1: 'text-h1 leading-[1.2] font-semibold',
|
|
84
|
-
h2: 'text-h2 leading-[1.3] font-semibold',
|
|
85
|
-
body: 'text-body leading-relaxed', // Tailwind: 1.625
|
|
86
|
-
small: 'text-small leading-snug', // Tailwind: 1.375
|
|
87
|
-
} as const
|
|
88
|
-
|
|
89
|
-
type TypographyVariant = keyof typeof styles
|
|
90
|
-
|
|
91
|
-
interface Props {
|
|
92
|
-
as?: React.ElementType
|
|
93
|
-
variant: TypographyVariant
|
|
94
|
-
children: React.ReactNode
|
|
95
|
-
className?: string
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export function Text({ as: Tag = 'p', variant, children, className }: Props): React.ReactElement {
|
|
99
|
-
return <Tag className={`${styles[variant]} ${className ?? ''}`}>{children}</Tag>
|
|
100
|
-
}
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
**DO:** Set heading line height in unitless values (e.g., `1.2`) so it scales proportionally with font size.
|
|
104
|
-
|
|
105
|
-
**DON'T:** Use fixed pixel line heights (e.g., `line-height: 24px`) on headings — they break when font size changes.
|
|
106
|
-
|
|
107
|
-
---
|
|
108
|
-
|
|
109
|
-
## Line Length
|
|
110
|
-
|
|
111
|
-
The measure (line length) is the single most impactful readability setting that developers routinely ignore. The optimal range for sustained reading is **45 to 75 characters**, with **65ch** as the recommended default.
|
|
112
|
-
|
|
113
|
-
Why this range? The eye must travel back to find the next line. Too short (under 45ch) causes excessive eye movement; too long (over 90ch) makes it easy to lose the current line.
|
|
114
|
-
|
|
115
|
-
**Framework-agnostic CSS:**
|
|
116
|
-
|
|
117
|
-
```css
|
|
118
|
-
.prose {
|
|
119
|
-
max-width: 65ch;
|
|
120
|
-
}
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
**Important:** `ch` is the width of the "0" character in the current font. At 16px in most system fonts this approximates 8–9px per character, making `65ch` roughly 520–585px — which aligns with most article layouts.
|
|
124
|
-
|
|
125
|
-
**Tailwind utility:**
|
|
126
|
-
|
|
127
|
-
```tsx
|
|
128
|
-
<article className="max-w-prose mx-auto px-4">
|
|
129
|
-
{/* Tailwind's max-w-prose = 65ch */}
|
|
130
|
-
<p className="text-body leading-relaxed">{content}</p>
|
|
131
|
-
</article>
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
Tailwind's built-in `max-w-prose` is exactly `65ch`. Use it for all long-form text containers.
|
|
135
|
-
|
|
136
|
-
**Special cases:**
|
|
137
|
-
- **Navigation and UI labels:** No line-length constraint; they are not reading contexts.
|
|
138
|
-
- **Captions under images:** Allow the caption to match the image width, which may exceed 65ch. This is intentional — captions are scanned, not read.
|
|
139
|
-
- **Data tables:** Disable max-width on table cells; column widths are data-driven.
|
|
140
|
-
|
|
141
|
-
**DO:** Apply `max-width: 65ch` to every article, blog post, and form description paragraph.
|
|
142
|
-
|
|
143
|
-
**DON'T:** Constrain the entire page layout to 65ch — constrain only the text container within it.
|
|
144
|
-
|
|
145
|
-
---
|
|
146
|
-
|
|
147
|
-
## Font Pairing
|
|
148
|
-
|
|
149
|
-
Pairing two typefaces creates typographic contrast that signals hierarchy without relying on size alone. The guiding principle is **contrast through difference, harmony through relationship**.
|
|
150
|
-
|
|
151
|
-
**Contrast principle:** Pair a serif with a sans-serif. Their structural difference is immediately legible, signaling "this is a heading" versus "this is body text" even before the reader processes the size difference.
|
|
152
|
-
|
|
153
|
-
**X-height matching:** When two typefaces share a similar x-height (the height of lowercase letters relative to the cap height), they feel proportionally balanced when placed side by side. Mismatched x-heights make one font look shrunken next to the other even at identical `font-size`.
|
|
154
|
-
|
|
155
|
-
**Reliable pairing patterns:**
|
|
156
|
-
|
|
157
|
-
| Heading (serif) | Body (sans-serif) | Character |
|
|
158
|
-
|-------------------------|-------------------------|----------------------------------|
|
|
159
|
-
| Playfair Display | Source Sans Pro | Editorial, editorial-minimal |
|
|
160
|
-
| Merriweather | Inter | Legible, neutral, very readable |
|
|
161
|
-
| Georgia (system) | -apple-system stack | Zero-load, high compatibility |
|
|
162
|
-
|
|
163
|
-
**Inverse approach — sans heading + body serif:**
|
|
164
|
-
|
|
165
|
-
| Heading (sans-serif) | Body (serif) | Character |
|
|
166
|
-
|-------------------------|-------------------------|----------------------------------|
|
|
167
|
-
| Inter | Georgia | Modern with warmth |
|
|
168
|
-
| DM Sans | Lora | Friendly + literary |
|
|
169
|
-
|
|
170
|
-
**Tailwind implementation:**
|
|
171
|
-
|
|
172
|
-
```css
|
|
173
|
-
/* globals.css */
|
|
174
|
-
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700&family=Inter:wght@400;500&display=swap');
|
|
175
|
-
|
|
176
|
-
:root {
|
|
177
|
-
--font-heading: 'Playfair Display', Georgia, serif;
|
|
178
|
-
--font-body: 'Inter', system-ui, -apple-system, sans-serif;
|
|
179
|
-
}
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
```js
|
|
183
|
-
// tailwind.config.js
|
|
184
|
-
module.exports = {
|
|
185
|
-
theme: {
|
|
186
|
-
extend: {
|
|
187
|
-
fontFamily: {
|
|
188
|
-
heading: 'var(--font-heading)',
|
|
189
|
-
body: 'var(--font-body)',
|
|
190
|
-
},
|
|
191
|
-
},
|
|
192
|
-
},
|
|
193
|
-
}
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
**DO:** Test the pairing at actual content — not just "Aa". Real words reveal whether x-heights are compatible.
|
|
197
|
-
|
|
198
|
-
**DON'T:** Pair two decorative or expressive typefaces. One of the pair must be neutral.
|
|
199
|
-
|
|
200
|
-
---
|
|
201
|
-
|
|
202
|
-
## Font Loading
|
|
203
|
-
|
|
204
|
-
How fonts load directly affects Cumulative Layout Shift (CLS) and First Contentful Paint (FCP). Poor loading causes text to flash invisible or reflow the layout.
|
|
205
|
-
|
|
206
|
-
**WOFF2 first:** WOFF2 provides 30% better compression than WOFF and is supported by all modern browsers. Always list WOFF2 as the first format source.
|
|
207
|
-
|
|
208
|
-
```css
|
|
209
|
-
@font-face {
|
|
210
|
-
font-family: 'Inter';
|
|
211
|
-
src:
|
|
212
|
-
url('/fonts/inter-v13-latin-regular.woff2') format('woff2'),
|
|
213
|
-
url('/fonts/inter-v13-latin-regular.woff') format('woff');
|
|
214
|
-
font-weight: 400;
|
|
215
|
-
font-style: normal;
|
|
216
|
-
font-display: swap;
|
|
217
|
-
}
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
**`font-display: swap`:** Immediately renders fallback text using the system font, then swaps to the web font once it loads. This eliminates the invisible text flash (FOIT) at the cost of a brief layout shift (FOTS). For most interfaces, this trade-off favors readability.
|
|
221
|
-
|
|
222
|
-
`font-display` options:
|
|
223
|
-
- `swap` — show fallback immediately, swap when ready (recommended for body text)
|
|
224
|
-
- `optional` — browser may not load if connection is slow (good for decorative fonts)
|
|
225
|
-
- `block` — brief invisible period (avoid; harms perceived performance)
|
|
226
|
-
|
|
227
|
-
**Subsetting:** Google Fonts and custom font files include glyphs for dozens of languages you may not need. Use `unicode-range` or a subsetting tool (e.g., `pyftsubset`, Fonttools) to ship only the characters your UI uses.
|
|
228
|
-
|
|
229
|
-
```css
|
|
230
|
-
@font-face {
|
|
231
|
-
font-family: 'Inter';
|
|
232
|
-
src: url('/fonts/inter-latin.woff2') format('woff2');
|
|
233
|
-
unicode-range: U+0020-007F, U+00A0-00FF; /* Basic Latin + Latin-1 */
|
|
234
|
-
font-display: swap;
|
|
235
|
-
}
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
**Preloading critical fonts:**
|
|
239
|
-
|
|
240
|
-
```html
|
|
241
|
-
<link
|
|
242
|
-
rel="preload"
|
|
243
|
-
href="/fonts/inter-latin.woff2"
|
|
244
|
-
as="font"
|
|
245
|
-
type="font/woff2"
|
|
246
|
-
crossorigin
|
|
247
|
-
/>
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
Preload only 1–2 fonts (the ones used above the fold). Preloading everything defeats the purpose.
|
|
251
|
-
|
|
252
|
-
**Next.js / React approach using `next/font`:**
|
|
253
|
-
|
|
254
|
-
```tsx
|
|
255
|
-
// app/layout.tsx
|
|
256
|
-
import { Inter, Playfair_Display } from 'next/font/google'
|
|
257
|
-
|
|
258
|
-
const inter = Inter({
|
|
259
|
-
subsets: ['latin'],
|
|
260
|
-
variable: '--font-body',
|
|
261
|
-
display: 'swap',
|
|
262
|
-
})
|
|
263
|
-
|
|
264
|
-
const playfair = Playfair_Display({
|
|
265
|
-
subsets: ['latin'],
|
|
266
|
-
variable: '--font-heading',
|
|
267
|
-
display: 'swap',
|
|
268
|
-
weight: ['700'],
|
|
269
|
-
})
|
|
270
|
-
|
|
271
|
-
export default function RootLayout({ children }: { children: React.ReactNode }): React.ReactElement {
|
|
272
|
-
return (
|
|
273
|
-
<html lang="en" className={`${inter.variable} ${playfair.variable}`}>
|
|
274
|
-
<body>{children}</body>
|
|
275
|
-
</html>
|
|
276
|
-
)
|
|
277
|
-
}
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
**DO:** Self-host fonts or use `next/font` (which inlines critical CSS and serves from the same origin). Both eliminate cross-origin DNS lookup latency.
|
|
281
|
-
|
|
282
|
-
**DON'T:** Load four weights of a font when you use two. Each weight is a separate network request.
|
|
283
|
-
|
|
284
|
-
---
|
|
285
|
-
|
|
286
|
-
## OpenType Features
|
|
287
|
-
|
|
288
|
-
OpenType features are typographic refinements baked into font files but disabled by default. Enabling them selectively improves quality without adding weight.
|
|
289
|
-
|
|
290
|
-
**CSS property:** `font-feature-settings`
|
|
291
|
-
|
|
292
|
-
Syntax uses four-letter feature tags as strings:
|
|
293
|
-
|
|
294
|
-
```css
|
|
295
|
-
.prose {
|
|
296
|
-
font-feature-settings:
|
|
297
|
-
"kern" 1, /* Kerning — always enable */
|
|
298
|
-
"liga" 1, /* Standard ligatures (fi, fl, ff) */
|
|
299
|
-
"calt" 1; /* Contextual alternates */
|
|
300
|
-
}
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
**Key features for UI work:**
|
|
304
|
-
|
|
305
|
-
| Feature tag | Name | Use case |
|
|
306
|
-
|-------------|----------------------|--------------------------------------------------|
|
|
307
|
-
| `kern` | Kerning | Always on; adjusts letter spacing between pairs |
|
|
308
|
-
| `liga` | Standard ligatures | Body text; replaces fi, fl, ff with single glyphs|
|
|
309
|
-
| `tnum` | Tabular numbers | Data tables, pricing, financial figures |
|
|
310
|
-
| `onum` | Oldstyle numbers | Body text; numbers that sit within lowercase flow|
|
|
311
|
-
| `lnum` | Lining numbers | Headings; all-caps contexts |
|
|
312
|
-
| `frac` | Fractions | Recipe text, measurements |
|
|
313
|
-
| `smcp` | Small caps | Acronyms in body text (CIA, NASA) |
|
|
314
|
-
|
|
315
|
-
**Tabular numbers are critical for tables:**
|
|
316
|
-
|
|
317
|
-
```tsx
|
|
318
|
-
// PriceCell.tsx
|
|
319
|
-
interface PriceCellProps {
|
|
320
|
-
amount: number
|
|
321
|
-
currency?: string
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
export function PriceCell({ amount, currency = 'USD' }: PriceCellProps): React.ReactElement {
|
|
325
|
-
const formatted = new Intl.NumberFormat('en-US', {
|
|
326
|
-
style: 'currency',
|
|
327
|
-
currency,
|
|
328
|
-
minimumFractionDigits: 2,
|
|
329
|
-
}).format(amount)
|
|
330
|
-
|
|
331
|
-
return (
|
|
332
|
-
<td
|
|
333
|
-
className="text-right tabular-nums font-mono"
|
|
334
|
-
style={{ fontFeatureSettings: '"tnum" 1, "lnum" 1' }}
|
|
335
|
-
>
|
|
336
|
-
{formatted}
|
|
337
|
-
</td>
|
|
338
|
-
)
|
|
339
|
-
}
|
|
340
|
-
```
|
|
341
|
-
|
|
342
|
-
Tailwind includes `tabular-nums` as a utility class (`font-variant-numeric: tabular-nums`), which is preferable to raw `font-feature-settings` when available.
|
|
343
|
-
|
|
344
|
-
**DO:** Enable `tnum` on every number column in tables and dashboards. Proportional numbers in columns produce ragged alignment that erodes trust in data.
|
|
345
|
-
|
|
346
|
-
**DON'T:** Enable `liga` on user-generated content or code blocks — ligatures can change the apparent character sequence.
|
|
347
|
-
|
|
348
|
-
---
|
|
349
|
-
|
|
350
|
-
## Accessibility
|
|
351
|
-
|
|
352
|
-
Typography accessibility is not an afterthought — it is a precondition. WCAG 2.1 AA defines minimum standards that protect users with low vision, cognitive differences, and situational impairments (bright sunlight, small screens).
|
|
353
|
-
|
|
354
|
-
**Minimum body font size: 16px (1rem)**
|
|
355
|
-
|
|
356
|
-
The browser default is 16px. Never override it downward with `html { font-size: 14px }` or similar. Many users set their browser base size larger for accessibility reasons — overriding it with a fixed pixel value breaks that accommodation.
|
|
357
|
-
|
|
358
|
-
```css
|
|
359
|
-
/* Correct: relative scaling respects user preferences */
|
|
360
|
-
html {
|
|
361
|
-
font-size: 100%; /* = user's browser setting, typically 16px */
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
body {
|
|
365
|
-
font-size: 1rem; /* scales with html */
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
/* Wrong: locks out users who have set a larger base */
|
|
369
|
-
html {
|
|
370
|
-
font-size: 14px;
|
|
371
|
-
}
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
**Use `rem` for font sizes, `em` for component-local spacing:**
|
|
375
|
-
|
|
376
|
-
```css
|
|
377
|
-
/* rem: relative to root — good for font sizes, global spacing */
|
|
378
|
-
h1 { font-size: 1.953rem; }
|
|
379
|
-
|
|
380
|
-
/* em: relative to current font size — good for padding around text */
|
|
381
|
-
button { padding: 0.5em 1em; }
|
|
382
|
-
```
|
|
383
|
-
|
|
384
|
-
**200% zoom support (WCAG 1.4.4 — Resize Text, Level AA):**
|
|
385
|
-
|
|
386
|
-
Content must be readable and functional when the browser is zoomed to 200%. This requirement fails when:
|
|
387
|
-
- Fixed-width containers clip text
|
|
388
|
-
- Absolute-positioned overlays cover content
|
|
389
|
-
- `overflow: hidden` truncates zoomed text
|
|
390
|
-
|
|
391
|
-
Test by pressing `Cmd+` (Mac) or `Ctrl+` (Windows) five times and verifying no content is cut off.
|
|
392
|
-
|
|
393
|
-
**Color contrast for text (WCAG 1.4.3 — Level AA):**
|
|
394
|
-
|
|
395
|
-
| Text size | Minimum contrast ratio |
|
|
396
|
-
|--------------------|------------------------|
|
|
397
|
-
| Normal text (<18pt) | 4.5:1 |
|
|
398
|
-
| Large text (≥18pt, or ≥14pt bold) | 3:1 |
|
|
399
|
-
|
|
400
|
-
Common failure: light gray text on white for placeholder, caption, or disabled states.
|
|
401
|
-
|
|
402
|
-
```tsx
|
|
403
|
-
// Accessible caption: contrast ≥ 4.5:1 against white background
|
|
404
|
-
// text-gray-500 (#6B7280) on white = 4.61:1 — passes AA
|
|
405
|
-
// text-gray-400 (#9CA3AF) on white = 2.85:1 — fails AA
|
|
406
|
-
|
|
407
|
-
<figcaption className="text-sm text-gray-500 mt-2">
|
|
408
|
-
Figure 1: Monthly active users by region
|
|
409
|
-
</figcaption>
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
**Focus visibility for text links:** Links must have a visible focus indicator that does not rely on color alone. Do not remove `outline` without providing an equivalent visual replacement.
|
|
413
|
-
|
|
414
|
-
**Letter spacing and `not-italic`:** Users with dyslexia benefit from increased letter spacing. Avoid reducing letter spacing below the font's default. The `prefers-reduced-motion` media query is unrelated to typography but lives in the same accessibility layer — be aware of it when using animated text effects.
|
|
415
|
-
|
|
416
|
-
**Tailwind accessibility checklist for text:**
|
|
417
|
-
|
|
418
|
-
```tsx
|
|
419
|
-
// Accessible article layout
|
|
420
|
-
<article className="
|
|
421
|
-
max-w-prose /* 65ch line length */
|
|
422
|
-
mx-auto
|
|
423
|
-
px-4
|
|
424
|
-
text-base /* 1rem = 16px */
|
|
425
|
-
leading-relaxed /* 1.625 line height */
|
|
426
|
-
text-gray-900 /* high contrast on white */
|
|
427
|
-
">
|
|
428
|
-
<h1 className="text-h1 leading-tight font-bold text-gray-950 mb-4">
|
|
429
|
-
{title}
|
|
430
|
-
</h1>
|
|
431
|
-
<p className="mb-4">{body}</p>
|
|
432
|
-
<small className="text-sm text-gray-500">{caption}</small>
|
|
433
|
-
</article>
|
|
434
|
-
```
|
|
435
|
-
|
|
436
|
-
**DO:** Test your type scale with real browser zoom (200%), a screen reader, and a contrast checker on every text color in your design system.
|
|
437
|
-
|
|
438
|
-
**DON'T:** Use `font-size` in `px` for body text. It severs the connection to the user's preferred base size and is a WCAG failure under 1.4.4.
|
|
439
|
-
|
|
440
|
-
---
|
|
441
|
-
|
|
442
|
-
## Quick Reference
|
|
443
|
-
|
|
444
|
-
| Setting | Recommended Value | WCAG Relevance |
|
|
445
|
-
|-----------------|-----------------------|------------------------|
|
|
446
|
-
| Body font size | ≥ 1rem (≥ 16px) | 1.4.4 Resize Text |
|
|
447
|
-
| Body line height| 1.5 – 1.75 | Readability (AAA: 1.5) |
|
|
448
|
-
| Heading leading | 1.1 – 1.3 | — |
|
|
449
|
-
| Line length | 45–75ch, default 65ch | Readability (AAA: 80ch max) |
|
|
450
|
-
| Scale sizes | ≤ 5 steps | — |
|
|
451
|
-
| Font units | `rem` for size | 1.4.4 Resize Text |
|
|
452
|
-
| Normal text contrast | ≥ 4.5:1 | 1.4.3 Contrast (AA) |
|
|
453
|
-
| Large text contrast | ≥ 3:1 | 1.4.3 Contrast (AA) |
|
|
454
|
-
| Font format | WOFF2 first | Performance |
|
|
455
|
-
| Font display | `swap` | Performance / CLS |
|
|
1
|
+
# Typography Systems for Web UI
|
|
2
|
+
|
|
3
|
+
A deep reference for building consistent, accessible, and visually compelling typography in production web interfaces.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Modular Scale
|
|
8
|
+
|
|
9
|
+
A modular scale establishes a mathematical relationship between type sizes, making every heading and body size feel intentional rather than arbitrary. The scale is driven by a **base size** (typically 16px) and a **ratio** that multiplies or divides to produce each step.
|
|
10
|
+
|
|
11
|
+
Common ratios:
|
|
12
|
+
- **1.25** (Major Third) — subtle, compact; good for dense UIs and dashboards
|
|
13
|
+
- **1.333** (Perfect Fourth) — balanced; versatile across marketing and application UIs
|
|
14
|
+
- **1.414** (Augmented Fourth / √2) — noticeable contrast; suits editorial layouts
|
|
15
|
+
- **1.5** (Perfect Fifth) — dramatic; use for landing pages, not data-heavy apps
|
|
16
|
+
|
|
17
|
+
**Limit yourself to five sizes or fewer.** Each size must serve a clear semantic role:
|
|
18
|
+
|
|
19
|
+
| Token | Role | 1.25 ratio | 1.333 ratio |
|
|
20
|
+
|-------------|-------------------|------------|-------------|
|
|
21
|
+
| `display` | Hero headlines | ~39px | ~48px |
|
|
22
|
+
| `h1` | Page title | ~31px | ~36px |
|
|
23
|
+
| `h2` | Section heading | ~25px | ~27px |
|
|
24
|
+
| `body` | Reading text | 16px | 16px |
|
|
25
|
+
| `small` | Captions, labels | ~13px | ~12px |
|
|
26
|
+
|
|
27
|
+
**Tailwind CSS approach** using CSS custom properties:
|
|
28
|
+
|
|
29
|
+
```css
|
|
30
|
+
/* globals.css */
|
|
31
|
+
:root {
|
|
32
|
+
--text-display: clamp(2rem, 5vw, 2.441rem);
|
|
33
|
+
--text-h1: clamp(1.75rem, 4vw, 1.953rem);
|
|
34
|
+
--text-h2: clamp(1.375rem, 3vw, 1.563rem);
|
|
35
|
+
--text-body: 1rem;
|
|
36
|
+
--text-small: 0.8rem;
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
// tailwind.config.js
|
|
42
|
+
module.exports = {
|
|
43
|
+
theme: {
|
|
44
|
+
extend: {
|
|
45
|
+
fontSize: {
|
|
46
|
+
display: 'var(--text-display)',
|
|
47
|
+
h1: 'var(--text-h1)',
|
|
48
|
+
h2: 'var(--text-h2)',
|
|
49
|
+
body: 'var(--text-body)',
|
|
50
|
+
small: 'var(--text-small)',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**DO:** Pick one ratio and apply it globally. Consistency builds visual rhythm.
|
|
58
|
+
|
|
59
|
+
**DON'T:** Mix ratios or hand-pick arbitrary pixel values (e.g., 14, 17, 22, 28, 38). This creates visual noise that readers feel even when they cannot name it.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Line Height
|
|
64
|
+
|
|
65
|
+
Line height (leading) controls the breathing room between lines of text. Too tight and lines collide; too loose and the text reads as disconnected fragments.
|
|
66
|
+
|
|
67
|
+
**Recommended values:**
|
|
68
|
+
|
|
69
|
+
| Context | Line Height Range | Reasoning |
|
|
70
|
+
|-----------------|-------------------|----------------------------------------------------|
|
|
71
|
+
| Body text | 1.5 – 1.75 | Sustained reading needs air; 1.6 is a safe default |
|
|
72
|
+
| Headings | 1.1 – 1.3 | Short lines at large sizes; tighter leading feels intentional |
|
|
73
|
+
| Captions/labels | 1.3 – 1.5 | Short, single-line; slightly relaxed helps scanning |
|
|
74
|
+
|
|
75
|
+
The reason headings use tighter leading: at large sizes the optical gap between lines grows significantly. A display heading at 40px with `line-height: 1.5` produces 20px of space — far too much for two or three words.
|
|
76
|
+
|
|
77
|
+
**React + Tailwind implementation:**
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
// Typography.tsx
|
|
81
|
+
const styles = {
|
|
82
|
+
display: 'text-display leading-[1.1] font-bold',
|
|
83
|
+
h1: 'text-h1 leading-[1.2] font-semibold',
|
|
84
|
+
h2: 'text-h2 leading-[1.3] font-semibold',
|
|
85
|
+
body: 'text-body leading-relaxed', // Tailwind: 1.625
|
|
86
|
+
small: 'text-small leading-snug', // Tailwind: 1.375
|
|
87
|
+
} as const
|
|
88
|
+
|
|
89
|
+
type TypographyVariant = keyof typeof styles
|
|
90
|
+
|
|
91
|
+
interface Props {
|
|
92
|
+
as?: React.ElementType
|
|
93
|
+
variant: TypographyVariant
|
|
94
|
+
children: React.ReactNode
|
|
95
|
+
className?: string
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function Text({ as: Tag = 'p', variant, children, className }: Props): React.ReactElement {
|
|
99
|
+
return <Tag className={`${styles[variant]} ${className ?? ''}`}>{children}</Tag>
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**DO:** Set heading line height in unitless values (e.g., `1.2`) so it scales proportionally with font size.
|
|
104
|
+
|
|
105
|
+
**DON'T:** Use fixed pixel line heights (e.g., `line-height: 24px`) on headings — they break when font size changes.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Line Length
|
|
110
|
+
|
|
111
|
+
The measure (line length) is the single most impactful readability setting that developers routinely ignore. The optimal range for sustained reading is **45 to 75 characters**, with **65ch** as the recommended default.
|
|
112
|
+
|
|
113
|
+
Why this range? The eye must travel back to find the next line. Too short (under 45ch) causes excessive eye movement; too long (over 90ch) makes it easy to lose the current line.
|
|
114
|
+
|
|
115
|
+
**Framework-agnostic CSS:**
|
|
116
|
+
|
|
117
|
+
```css
|
|
118
|
+
.prose {
|
|
119
|
+
max-width: 65ch;
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Important:** `ch` is the width of the "0" character in the current font. At 16px in most system fonts this approximates 8–9px per character, making `65ch` roughly 520–585px — which aligns with most article layouts.
|
|
124
|
+
|
|
125
|
+
**Tailwind utility:**
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
<article className="max-w-prose mx-auto px-4">
|
|
129
|
+
{/* Tailwind's max-w-prose = 65ch */}
|
|
130
|
+
<p className="text-body leading-relaxed">{content}</p>
|
|
131
|
+
</article>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Tailwind's built-in `max-w-prose` is exactly `65ch`. Use it for all long-form text containers.
|
|
135
|
+
|
|
136
|
+
**Special cases:**
|
|
137
|
+
- **Navigation and UI labels:** No line-length constraint; they are not reading contexts.
|
|
138
|
+
- **Captions under images:** Allow the caption to match the image width, which may exceed 65ch. This is intentional — captions are scanned, not read.
|
|
139
|
+
- **Data tables:** Disable max-width on table cells; column widths are data-driven.
|
|
140
|
+
|
|
141
|
+
**DO:** Apply `max-width: 65ch` to every article, blog post, and form description paragraph.
|
|
142
|
+
|
|
143
|
+
**DON'T:** Constrain the entire page layout to 65ch — constrain only the text container within it.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Font Pairing
|
|
148
|
+
|
|
149
|
+
Pairing two typefaces creates typographic contrast that signals hierarchy without relying on size alone. The guiding principle is **contrast through difference, harmony through relationship**.
|
|
150
|
+
|
|
151
|
+
**Contrast principle:** Pair a serif with a sans-serif. Their structural difference is immediately legible, signaling "this is a heading" versus "this is body text" even before the reader processes the size difference.
|
|
152
|
+
|
|
153
|
+
**X-height matching:** When two typefaces share a similar x-height (the height of lowercase letters relative to the cap height), they feel proportionally balanced when placed side by side. Mismatched x-heights make one font look shrunken next to the other even at identical `font-size`.
|
|
154
|
+
|
|
155
|
+
**Reliable pairing patterns:**
|
|
156
|
+
|
|
157
|
+
| Heading (serif) | Body (sans-serif) | Character |
|
|
158
|
+
|-------------------------|-------------------------|----------------------------------|
|
|
159
|
+
| Playfair Display | Source Sans Pro | Editorial, editorial-minimal |
|
|
160
|
+
| Merriweather | Inter | Legible, neutral, very readable |
|
|
161
|
+
| Georgia (system) | -apple-system stack | Zero-load, high compatibility |
|
|
162
|
+
|
|
163
|
+
**Inverse approach — sans heading + body serif:**
|
|
164
|
+
|
|
165
|
+
| Heading (sans-serif) | Body (serif) | Character |
|
|
166
|
+
|-------------------------|-------------------------|----------------------------------|
|
|
167
|
+
| Inter | Georgia | Modern with warmth |
|
|
168
|
+
| DM Sans | Lora | Friendly + literary |
|
|
169
|
+
|
|
170
|
+
**Tailwind implementation:**
|
|
171
|
+
|
|
172
|
+
```css
|
|
173
|
+
/* globals.css */
|
|
174
|
+
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700&family=Inter:wght@400;500&display=swap');
|
|
175
|
+
|
|
176
|
+
:root {
|
|
177
|
+
--font-heading: 'Playfair Display', Georgia, serif;
|
|
178
|
+
--font-body: 'Inter', system-ui, -apple-system, sans-serif;
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
```js
|
|
183
|
+
// tailwind.config.js
|
|
184
|
+
module.exports = {
|
|
185
|
+
theme: {
|
|
186
|
+
extend: {
|
|
187
|
+
fontFamily: {
|
|
188
|
+
heading: 'var(--font-heading)',
|
|
189
|
+
body: 'var(--font-body)',
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**DO:** Test the pairing at actual content — not just "Aa". Real words reveal whether x-heights are compatible.
|
|
197
|
+
|
|
198
|
+
**DON'T:** Pair two decorative or expressive typefaces. One of the pair must be neutral.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Font Loading
|
|
203
|
+
|
|
204
|
+
How fonts load directly affects Cumulative Layout Shift (CLS) and First Contentful Paint (FCP). Poor loading causes text to flash invisible or reflow the layout.
|
|
205
|
+
|
|
206
|
+
**WOFF2 first:** WOFF2 provides 30% better compression than WOFF and is supported by all modern browsers. Always list WOFF2 as the first format source.
|
|
207
|
+
|
|
208
|
+
```css
|
|
209
|
+
@font-face {
|
|
210
|
+
font-family: 'Inter';
|
|
211
|
+
src:
|
|
212
|
+
url('/fonts/inter-v13-latin-regular.woff2') format('woff2'),
|
|
213
|
+
url('/fonts/inter-v13-latin-regular.woff') format('woff');
|
|
214
|
+
font-weight: 400;
|
|
215
|
+
font-style: normal;
|
|
216
|
+
font-display: swap;
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**`font-display: swap`:** Immediately renders fallback text using the system font, then swaps to the web font once it loads. This eliminates the invisible text flash (FOIT) at the cost of a brief layout shift (FOTS). For most interfaces, this trade-off favors readability.
|
|
221
|
+
|
|
222
|
+
`font-display` options:
|
|
223
|
+
- `swap` — show fallback immediately, swap when ready (recommended for body text)
|
|
224
|
+
- `optional` — browser may not load if connection is slow (good for decorative fonts)
|
|
225
|
+
- `block` — brief invisible period (avoid; harms perceived performance)
|
|
226
|
+
|
|
227
|
+
**Subsetting:** Google Fonts and custom font files include glyphs for dozens of languages you may not need. Use `unicode-range` or a subsetting tool (e.g., `pyftsubset`, Fonttools) to ship only the characters your UI uses.
|
|
228
|
+
|
|
229
|
+
```css
|
|
230
|
+
@font-face {
|
|
231
|
+
font-family: 'Inter';
|
|
232
|
+
src: url('/fonts/inter-latin.woff2') format('woff2');
|
|
233
|
+
unicode-range: U+0020-007F, U+00A0-00FF; /* Basic Latin + Latin-1 */
|
|
234
|
+
font-display: swap;
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**Preloading critical fonts:**
|
|
239
|
+
|
|
240
|
+
```html
|
|
241
|
+
<link
|
|
242
|
+
rel="preload"
|
|
243
|
+
href="/fonts/inter-latin.woff2"
|
|
244
|
+
as="font"
|
|
245
|
+
type="font/woff2"
|
|
246
|
+
crossorigin
|
|
247
|
+
/>
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Preload only 1–2 fonts (the ones used above the fold). Preloading everything defeats the purpose.
|
|
251
|
+
|
|
252
|
+
**Next.js / React approach using `next/font`:**
|
|
253
|
+
|
|
254
|
+
```tsx
|
|
255
|
+
// app/layout.tsx
|
|
256
|
+
import { Inter, Playfair_Display } from 'next/font/google'
|
|
257
|
+
|
|
258
|
+
const inter = Inter({
|
|
259
|
+
subsets: ['latin'],
|
|
260
|
+
variable: '--font-body',
|
|
261
|
+
display: 'swap',
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
const playfair = Playfair_Display({
|
|
265
|
+
subsets: ['latin'],
|
|
266
|
+
variable: '--font-heading',
|
|
267
|
+
display: 'swap',
|
|
268
|
+
weight: ['700'],
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
export default function RootLayout({ children }: { children: React.ReactNode }): React.ReactElement {
|
|
272
|
+
return (
|
|
273
|
+
<html lang="en" className={`${inter.variable} ${playfair.variable}`}>
|
|
274
|
+
<body>{children}</body>
|
|
275
|
+
</html>
|
|
276
|
+
)
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**DO:** Self-host fonts or use `next/font` (which inlines critical CSS and serves from the same origin). Both eliminate cross-origin DNS lookup latency.
|
|
281
|
+
|
|
282
|
+
**DON'T:** Load four weights of a font when you use two. Each weight is a separate network request.
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## OpenType Features
|
|
287
|
+
|
|
288
|
+
OpenType features are typographic refinements baked into font files but disabled by default. Enabling them selectively improves quality without adding weight.
|
|
289
|
+
|
|
290
|
+
**CSS property:** `font-feature-settings`
|
|
291
|
+
|
|
292
|
+
Syntax uses four-letter feature tags as strings:
|
|
293
|
+
|
|
294
|
+
```css
|
|
295
|
+
.prose {
|
|
296
|
+
font-feature-settings:
|
|
297
|
+
"kern" 1, /* Kerning — always enable */
|
|
298
|
+
"liga" 1, /* Standard ligatures (fi, fl, ff) */
|
|
299
|
+
"calt" 1; /* Contextual alternates */
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**Key features for UI work:**
|
|
304
|
+
|
|
305
|
+
| Feature tag | Name | Use case |
|
|
306
|
+
|-------------|----------------------|--------------------------------------------------|
|
|
307
|
+
| `kern` | Kerning | Always on; adjusts letter spacing between pairs |
|
|
308
|
+
| `liga` | Standard ligatures | Body text; replaces fi, fl, ff with single glyphs|
|
|
309
|
+
| `tnum` | Tabular numbers | Data tables, pricing, financial figures |
|
|
310
|
+
| `onum` | Oldstyle numbers | Body text; numbers that sit within lowercase flow|
|
|
311
|
+
| `lnum` | Lining numbers | Headings; all-caps contexts |
|
|
312
|
+
| `frac` | Fractions | Recipe text, measurements |
|
|
313
|
+
| `smcp` | Small caps | Acronyms in body text (CIA, NASA) |
|
|
314
|
+
|
|
315
|
+
**Tabular numbers are critical for tables:**
|
|
316
|
+
|
|
317
|
+
```tsx
|
|
318
|
+
// PriceCell.tsx
|
|
319
|
+
interface PriceCellProps {
|
|
320
|
+
amount: number
|
|
321
|
+
currency?: string
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export function PriceCell({ amount, currency = 'USD' }: PriceCellProps): React.ReactElement {
|
|
325
|
+
const formatted = new Intl.NumberFormat('en-US', {
|
|
326
|
+
style: 'currency',
|
|
327
|
+
currency,
|
|
328
|
+
minimumFractionDigits: 2,
|
|
329
|
+
}).format(amount)
|
|
330
|
+
|
|
331
|
+
return (
|
|
332
|
+
<td
|
|
333
|
+
className="text-right tabular-nums font-mono"
|
|
334
|
+
style={{ fontFeatureSettings: '"tnum" 1, "lnum" 1' }}
|
|
335
|
+
>
|
|
336
|
+
{formatted}
|
|
337
|
+
</td>
|
|
338
|
+
)
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Tailwind includes `tabular-nums` as a utility class (`font-variant-numeric: tabular-nums`), which is preferable to raw `font-feature-settings` when available.
|
|
343
|
+
|
|
344
|
+
**DO:** Enable `tnum` on every number column in tables and dashboards. Proportional numbers in columns produce ragged alignment that erodes trust in data.
|
|
345
|
+
|
|
346
|
+
**DON'T:** Enable `liga` on user-generated content or code blocks — ligatures can change the apparent character sequence.
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
## Accessibility
|
|
351
|
+
|
|
352
|
+
Typography accessibility is not an afterthought — it is a precondition. WCAG 2.1 AA defines minimum standards that protect users with low vision, cognitive differences, and situational impairments (bright sunlight, small screens).
|
|
353
|
+
|
|
354
|
+
**Minimum body font size: 16px (1rem)**
|
|
355
|
+
|
|
356
|
+
The browser default is 16px. Never override it downward with `html { font-size: 14px }` or similar. Many users set their browser base size larger for accessibility reasons — overriding it with a fixed pixel value breaks that accommodation.
|
|
357
|
+
|
|
358
|
+
```css
|
|
359
|
+
/* Correct: relative scaling respects user preferences */
|
|
360
|
+
html {
|
|
361
|
+
font-size: 100%; /* = user's browser setting, typically 16px */
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
body {
|
|
365
|
+
font-size: 1rem; /* scales with html */
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/* Wrong: locks out users who have set a larger base */
|
|
369
|
+
html {
|
|
370
|
+
font-size: 14px;
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
**Use `rem` for font sizes, `em` for component-local spacing:**
|
|
375
|
+
|
|
376
|
+
```css
|
|
377
|
+
/* rem: relative to root — good for font sizes, global spacing */
|
|
378
|
+
h1 { font-size: 1.953rem; }
|
|
379
|
+
|
|
380
|
+
/* em: relative to current font size — good for padding around text */
|
|
381
|
+
button { padding: 0.5em 1em; }
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
**200% zoom support (WCAG 1.4.4 — Resize Text, Level AA):**
|
|
385
|
+
|
|
386
|
+
Content must be readable and functional when the browser is zoomed to 200%. This requirement fails when:
|
|
387
|
+
- Fixed-width containers clip text
|
|
388
|
+
- Absolute-positioned overlays cover content
|
|
389
|
+
- `overflow: hidden` truncates zoomed text
|
|
390
|
+
|
|
391
|
+
Test by pressing `Cmd+` (Mac) or `Ctrl+` (Windows) five times and verifying no content is cut off.
|
|
392
|
+
|
|
393
|
+
**Color contrast for text (WCAG 1.4.3 — Level AA):**
|
|
394
|
+
|
|
395
|
+
| Text size | Minimum contrast ratio |
|
|
396
|
+
|--------------------|------------------------|
|
|
397
|
+
| Normal text (<18pt) | 4.5:1 |
|
|
398
|
+
| Large text (≥18pt, or ≥14pt bold) | 3:1 |
|
|
399
|
+
|
|
400
|
+
Common failure: light gray text on white for placeholder, caption, or disabled states.
|
|
401
|
+
|
|
402
|
+
```tsx
|
|
403
|
+
// Accessible caption: contrast ≥ 4.5:1 against white background
|
|
404
|
+
// text-gray-500 (#6B7280) on white = 4.61:1 — passes AA
|
|
405
|
+
// text-gray-400 (#9CA3AF) on white = 2.85:1 — fails AA
|
|
406
|
+
|
|
407
|
+
<figcaption className="text-sm text-gray-500 mt-2">
|
|
408
|
+
Figure 1: Monthly active users by region
|
|
409
|
+
</figcaption>
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
**Focus visibility for text links:** Links must have a visible focus indicator that does not rely on color alone. Do not remove `outline` without providing an equivalent visual replacement.
|
|
413
|
+
|
|
414
|
+
**Letter spacing and `not-italic`:** Users with dyslexia benefit from increased letter spacing. Avoid reducing letter spacing below the font's default. The `prefers-reduced-motion` media query is unrelated to typography but lives in the same accessibility layer — be aware of it when using animated text effects.
|
|
415
|
+
|
|
416
|
+
**Tailwind accessibility checklist for text:**
|
|
417
|
+
|
|
418
|
+
```tsx
|
|
419
|
+
// Accessible article layout
|
|
420
|
+
<article className="
|
|
421
|
+
max-w-prose /* 65ch line length */
|
|
422
|
+
mx-auto
|
|
423
|
+
px-4
|
|
424
|
+
text-base /* 1rem = 16px */
|
|
425
|
+
leading-relaxed /* 1.625 line height */
|
|
426
|
+
text-gray-900 /* high contrast on white */
|
|
427
|
+
">
|
|
428
|
+
<h1 className="text-h1 leading-tight font-bold text-gray-950 mb-4">
|
|
429
|
+
{title}
|
|
430
|
+
</h1>
|
|
431
|
+
<p className="mb-4">{body}</p>
|
|
432
|
+
<small className="text-sm text-gray-500">{caption}</small>
|
|
433
|
+
</article>
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
**DO:** Test your type scale with real browser zoom (200%), a screen reader, and a contrast checker on every text color in your design system.
|
|
437
|
+
|
|
438
|
+
**DON'T:** Use `font-size` in `px` for body text. It severs the connection to the user's preferred base size and is a WCAG failure under 1.4.4.
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
## Quick Reference
|
|
443
|
+
|
|
444
|
+
| Setting | Recommended Value | WCAG Relevance |
|
|
445
|
+
|-----------------|-----------------------|------------------------|
|
|
446
|
+
| Body font size | ≥ 1rem (≥ 16px) | 1.4.4 Resize Text |
|
|
447
|
+
| Body line height| 1.5 – 1.75 | Readability (AAA: 1.5) |
|
|
448
|
+
| Heading leading | 1.1 – 1.3 | — |
|
|
449
|
+
| Line length | 45–75ch, default 65ch | Readability (AAA: 80ch max) |
|
|
450
|
+
| Scale sizes | ≤ 5 steps | — |
|
|
451
|
+
| Font units | `rem` for size | 1.4.4 Resize Text |
|
|
452
|
+
| Normal text contrast | ≥ 4.5:1 | 1.4.3 Contrast (AA) |
|
|
453
|
+
| Large text contrast | ≥ 3:1 | 1.4.3 Contrast (AA) |
|
|
454
|
+
| Font format | WOFF2 first | Performance |
|
|
455
|
+
| Font display | `swap` | Performance / CLS |
|