A typographic and data-visualization CSS library derived from the books of Edward Tufte, the cartographic color principles of Eduard Imhof, and the typographic standards of Robert Bringhurst.
- ET Book typeface -- Tufte's open-source Bembo digitisation
- Sidenotes & margin notes -- CSS-only, no JavaScript required
- Data visualization components -- dot charts, dumbbell charts, sparklines, bullet graphs, heat tables, slopegraphs, parallel coordinates, and more
- Narrative map framing -- reusable slippy-map container, inset legend panel, and map annotation chrome for MapLibre/Leaflet examples
- Analytical briefing blocks -- strategy headers, metric strips, tab rows, analytic cards, app-shell primitives, reusable research rows, and editorial spread layouts for research apps
- Analytics application guidance -- treemap framing, ranked-bar patterns, workspace shells, and table/chart rules informed by real dashboard integration work
- Chart interop tokens -- semantic chart colors and axis/grid variables for D3, Recharts, ECharts, and hand-rolled SVG
- Editorial spread layouts -- dual-column analysis blocks with deliberate chart/timeline breakout rows
- Theme-aware by default -- follows
prefers-color-scheme; manual override via.dark,.light, or[data-theme] - Responsive -- fluid typography, mobile sidenote toggles, adaptive chart layouts
- Print styles -- optimized for paper output
- CSS layers --
@layerfor specificity-safe cascade; override without!important - oklch() colors -- perceptually uniform categorical palette per Imhof's equal-weight principle
<link rel="stylesheet" href="https://unpkg.com/@andrewxhill/[email protected]/css/graphics-press.css">npm install @andrewxhill/graphics-press-css@import '@andrewxhill/graphics-press-css';npm install @andrewxhill/graphics-press-css tailwindcss// tailwind.config.js
module.exports = {
plugins: [
require('@andrewxhill/graphics-press-css/tailwind'),
],
}<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://unpkg.com/@andrewxhill/[email protected]/css/graphics-press.css">
</head>
<body>
<article>
<h1>Your Title</h1>
<p class="subtitle">Your subtitle here</p>
<section>
<h2>Section heading</h2>
<p>Your content with a sidenote.<label for="sn-1" class="sidenote-toggle sidenote-number"></label>
<input type="checkbox" id="sn-1" class="sidenote-toggle">
<span class="sidenote">This appears in the margin on wide screens.</span>
</p>
</section>
</article>
</body>
</html>| Component | Class | Description |
|---|---|---|
| Sidenotes | .sidenote |
Numbered margin notes |
| Margin notes | .marginnote |
Unnumbered margin content |
| Figures | .figure-full, .figure-margin |
Column, full-width, or margin figures |
| Tables | booktabs default | Three-rule tables in Gill Sans |
| Sparklines | .sparkline |
Word-sized inline charts |
| Dot chart | .dot-chart |
Tufte's bar chart replacement |
| Dumbbell | .dumbbell |
Before/after connected dots |
| Strip plot | .strip-plot |
One-way scatter |
| Bullet graph | .bullet-graphs |
Gauge replacement |
| Heat table | .heat-table |
Color-encoded cell values |
| Ranked table | .ranked-table |
Dotted leaders, recessive ranks |
| Small multiples | .small-multiples |
Grid of comparable charts |
| Timeline | .timeline |
Events and periods |
| Slopegraph | .slopegraph-wrap |
Two-point comparison |
| Parallel coords | .parallel-coords |
Multi-variable SVG |
| Slippy map | .gp-map-block, .gp-map-frame, .gp-map-panel |
Narrative map embed with overlay panel and key |
| Strategy brief | .brief-backlink, .brief-header, .metric-strip, .tab-nav--filled, .analytic-grid, .activity-timeline |
Analytical dashboard framing for research and trading apps |
| App UI | .gp-app-shell, .gp-app-main--detail, .gp-app-main--detail-wide, .gp-workspace, .gp-workspace__tabs, .gp-workspace__header, .gp-workspace__toolbar, .gp-toolbar, .gp-btn, .gp-input, .gp-panel, .gp-kpi-grid, .gp-data-table, .gp-card-grid, .gp-progress, .gp-meter, .gp-rank-list, .gp-rank-card, .gp-record-list, .gp-chart-frame, .gp-spread, .gp-treemap, .gp-treemap__tooltip |
Tufte-compatible operational UI layer for analytics apps, research lists, ranked bar views, chart wrappers, editorial spreads, wide workspace shells, treemaps, tooltips, and quantitative table cells |
| Bar chart | .bar-chart |
Minimal tick or filled bars |
| Pull stat | .pull-stat |
Large featured numbers |
| Stat grid | .stat-grid |
Multiple metrics under one rule |
| Evidence | .evidence |
Image + analysis layout |
Default behavior is automatic via system preference. Manual override is also supported:
document.documentElement.dataset.theme = 'dark'; // or 'light'
// `.dark` / `.light` classes also workThe default library voice is still serif-forward. If you want a cleaner application mode, tufte2 also supports an Inter + JetBrains Mono variation driven by next/font/google.
import { Inter, JetBrains_Mono } from "next/font/google";
const inter = Inter({
subsets: ["latin"],
variable: "--font-inter",
display: "swap",
});
const jetbrainsMono = JetBrains_Mono({
subsets: ["latin"],
variable: "--font-jetbrains-mono",
display: "swap",
});
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html
lang="en"
className={`${inter.variable} ${jetbrainsMono.variable} gp-font-inter`}
>
<body>{children}</body>
</html>
);
}You can also switch the variation with an attribute instead of a class:
<html
lang="en"
className={`${inter.variable} ${jetbrainsMono.variable}`}
data-gp-font="inter"
>That setup keeps font loading inside Next.js and avoids external font <link> tags entirely.
- Make hierarchy loud and decoration quiet. Grouping, proportion, spacing, and line weight should orient the reader before color does.
- Keep app shells wide and figures disciplined. Workspace headers can hold tabs, KPIs, and filters; the actual evidence should still read like charts, tables, and annotated figures, not rounded widgets.
- Use semantic chart tokens everywhere. Read
--gp-chart-positive,--gp-chart-negative,--gp-chart-accent-1,--gp-chart-axis,--gp-chart-grid, and--gp-chart-surfacefrom CSS and reuse them across D3, Recharts, ECharts, or SVG. - In dense treemaps, let category blocks carry the hierarchy and keep micro-cells quiet. Strong borders on every tiny tile create shimmer.
- In tables, keep the number visible and let the visual encoding support it.
gp-meteris for compact quantitative cells, not for hiding the value.
MIT
Releases are CI-driven with Changesets.
For any PR that should publish a new package version:
npx changesetChoose the release type:
patchfor fixes and polishminorfor new backwards-compatible components or tokensmajorfor breaking changes
When changesets land on main:
- GitHub Actions opens or updates a release PR
- merging that PR bumps the version and creates the release commit
- CI tags the release, publishes to npm, creates the GitHub release, and republishes
gh-pages
Do not hand-create version tags or GitHub releases for normal package publishing.