You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Make Open Library look and feel maintained, trustworthy, and usable everywhere — one design language across the site, accessible and mobile-solid, measured through Core Vitals.
Two things follow from that, and they run through every workstream below: content gets the screen, chrome gets out of the way. Headers, toolbars, filter rows, and droppers should take as few vertical pixels as they can and recede visually — consistent heights, one hover treatment, quiet colors — so the eye lands on books, not on the frame around them. Today chrome takes 62% of the viewport on mobile search results (#13482), and each surface invents its own control heights and hover states.
Inconsistent typography and dated UI signals "unmaintained." A patron who lands on a page with four heading styles, three button treatments, and a layout that breaks at 375px doesn't file a bug — they leave, and they don't trust us with their reading data. This work is the substrate every other H2 patron-facing initiative ships on: Prompts (#12858), the Genre Explorer (#13158), and carousel/browsing work (#9828, #10512) all need a component vocabulary and an accessibility baseline to build against. Doing it once here is cheaper than each of those inventing its own buttons, dialogs, and focus handling.
This epic supersedes #11555 (two-tiered design tokens) and absorbs its remaining checklist — see Foundation below.
Success criteria
Core Vitals (CLS/LCP/INP) tracked and non-regressing on the four highest-traffic templates: home, search, book, subject
No new raw hex colors, magic-number font sizes, or ad-hoc z-index values merged (enforced by stylelint, not by review vigilance)
Zero critical/serious axe violations on those same four templates
Every ol-* component has a docs page and a stated mobile + keyboard contract
In flight
Open PRs already doing this work:
Add unified icon system #12955 — Unified icon system: <ol-icon>, $:macros.icon() macro, build-time sprite (Foundation → components + docs). Replaces the PNG/one-off-SVG icon patchwork with 42 glyphs on one 24×24 grid, served as a single hashed sprite (1.9 KB gzipped); adopted across 49 call sites, with a searchable gallery at /developers/design/icons and 50 legacy image files deleted. Still in draft.
Rebuild ol-carousel on native scroll snapping #13220 — Rebuild ol-carousel on native scroll snapping (Mobile pass, + a11y). Replaces hand-rolled gesture/spring physics with platform scroll snapping: OS-native momentum and overscroll, mouse wheel works, off-page items no longer inert so they're reachable by tab, find-in-page, and screen readers. Net −132 lines in OlCarousel.js, plus a new unit-test suite. Supersedes ol-carousel touch gestures and swipe physic refinements #13101. Currently conflicts with master — the docs page it patches moved to templates/design/components/carousel.html.jinja.
Unify header nav #13508 — Unify the header nav into matching ghost buttons with sprite icons (Foundation → surfaces). One --header-control-height for the search field, Browse, My Books, and the account button; one ghost-button hover treatment across all three nav controls; every header glyph moved onto the Lucide sprite (including the archive.org avatar photo, dropping a third-party image request per logged-in page load). Nets 16px of vertical chrome back to content on every page (the row height itself is bound by the 47px logo, not the controls).
Add OlDrawer component for hamburger menu #12326 — ol-drawer for the hamburger menu (Foundation → components). Rebuilt on native <dialog>.showModal(), so the drawer gets the top layer, background inerting and Escape from the platform — the stacking-context problem solved without a z-index. Also surfaces Sign Up in the header below 480px, where the auth links were hidden entirely.
Recently landed: #13228 (search results toolbar onto web components, adding <ol-menu-popover> and the Details/Grid segmented control), #13238 (ol-paginationsize variants), #13102 (color tokens), #13247 (z-index tokens), #13006 (jest-axe CI gate).
Workstream 1 — Foundation
Design tokens, the ol-* web component family, and living docs. This is the part everything else depends on.
Two-tier structure — primitives (a small curated set of raw values) → semantic tokens (design intent, e.g. --radius-card, --color-surface-raised). Semantic tokens are what templates and components reference, so a category-wide change is a one-line edit. Tokens live in static/css/tokens/, aggregated by static/css/tokens.css.
20 Lit components exist today in openlibrary/components/lit/ — ol-button, ol-dialog, ol-popover, ol-select-popover, ol-options-popover, ol-menu-popover, ol-segmented-control, ol-toggle, ol-chip/ol-chip-group, ol-toast/ol-toast-region, ol-banner, ol-tooltip, ol-carousel, ol-pagination, ol-scorecard, ol-read-more, ol-markdown-editor, and others. The family is real; what's missing is consistency guarantees across it.
Shadow-boundary contract documented and honored across all components — focus/tabbing, CSS custom-property surface, form association, ARIA. Several components already implement pieces of this (form-associated mixin, non-modal popover); it isn't yet uniform or written down.
Lint the Lit css template literals.lint:css globs only static/**/*.css and openlibrary/**/*.css, so styles authored inside .js are unlinted — which is why 42 raw hexes survive in shadow DOM. Until this exists, "no new raw hex" is unenforceable there. Drafted in Stylelint coverage for Lit + Vue; retire raw hex from web components #13110 (closed).
Every component consumes semantic tokens only — no raw hex inside shadow DOM
Add font-size to stylelint's declaration-strict-value list once a type scale lands — currently the only success criterion with no enforcement path
Migrate any other legacy jQuery widgets that duplicate an existing ol-* component
Living docs
The design system docs live at /developers/design, assembled by openlibrary/plugins/openlibrary/design.py from 19 component pages under openlibrary/templates/design/components/, with tokens parsed straight out of the token CSS.
Token reference page — primitives and semantics, browsable
Each page states the mobile behavior and keyboard contract, not only the prop table — today 4 of 19 pages mention keyboard behavior and 2 mention mobile
Workstream 2 — A11y audit
A systematic WCAG 2.1 AA sweep, building on fixes already shipped rather than starting cold. Recently landed: search-modal dialog naming + arrow-key result nav + live region (52da434), ReadButton dropdown summary name (#13116), generic-dropper arrow button label (#13029), OlSelectPopoverrole=group (#13037), iframe titles (#13012), decorative star ratings hidden (#13028), several contrast fixes (#13030, #13027), and the interactive-element audits for the book page (#4906) and Want To Read (#4911).
Systematic page-by-page WCAG 2.1 AA audit — start with home, search, book, subject, My Books
The CI gate matters more than any individual fix: without it, this list regenerates itself every 18 months. jest-axe covers component ARIA patterns; #13251 extends that to whole pages.
Workstream 3 — Mobile pass
Touch behavior, keyboard handling, and layout at small sizes. Extends the shipped search-modal mobile work (full-height modal, equal-height filter row, text-entry auto-zoom rules) and carousel touch gestures (#13101).
Small-screen layout audit across primary templates — no horizontal overflow, no clipped controls
Core Vitals are the outside check on whether this is working, since "looks maintained" isn't measurable on its own:
CLS — reserved image space, no late-loading font swaps
LCP — self-hosted subset fonts, no render-blocking token cascade
INP — component interaction cost, especially carousels and poppers
Nothing measures these today — there's no vitals instrumentation in the repo yet. #13252 (read-only Matomo client) and #13213 (Core Vitals Retention Score) are the proposed reporting path. This ties into item 2 (Metrics/Core Vitals) of #13194.
Beforecreating a new branch or pushing up changes to a PR, please first run these commands to ensure your repository is up to date, as the pre-commit bot may add commits to your PRs upstream.
Summary
Make Open Library look and feel maintained, trustworthy, and usable everywhere — one design language across the site, accessible and mobile-solid, measured through Core Vitals.
Two things follow from that, and they run through every workstream below: content gets the screen, chrome gets out of the way. Headers, toolbars, filter rows, and droppers should take as few vertical pixels as they can and recede visually — consistent heights, one hover treatment, quiet colors — so the eye lands on books, not on the frame around them. Today chrome takes 62% of the viewport on mobile search results (#13482), and each surface invents its own control heights and hover states.
Inconsistent typography and dated UI signals "unmaintained." A patron who lands on a page with four heading styles, three button treatments, and a layout that breaks at 375px doesn't file a bug — they leave, and they don't trust us with their reading data. This work is the substrate every other H2 patron-facing initiative ships on: Prompts (#12858), the Genre Explorer (#13158), and carousel/browsing work (#9828, #10512) all need a component vocabulary and an accessibility baseline to build against. Doing it once here is cheaper than each of those inventing its own buttons, dialogs, and focus handling.
This epic supersedes #11555 (two-tiered design tokens) and absorbs its remaining checklist — see Foundation below.
Success criteria
ol-*component has a docs page and a stated mobile + keyboard contractIn flight
Open PRs already doing this work:
<ol-icon>,$:macros.icon()macro, build-time sprite (Foundation → components + docs). Replaces the PNG/one-off-SVG icon patchwork with 42 glyphs on one 24×24 grid, served as a single hashed sprite (1.9 KB gzipped); adopted across 49 call sites, with a searchable gallery at/developers/design/iconsand 50 legacy image files deleted. Still in draft.ol-carouselon native scroll snapping (Mobile pass, + a11y). Replaces hand-rolled gesture/spring physics with platform scroll snapping: OS-native momentum and overscroll, mouse wheel works, off-page items no longerinertso they're reachable by tab, find-in-page, and screen readers. Net −132 lines inOlCarousel.js, plus a new unit-test suite. Supersedes ol-carousel touch gestures and swipe physic refinements #13101. Currently conflicts with master — the docs page it patches moved totemplates/design/components/carousel.html.jinja.--header-control-heightfor the search field, Browse, My Books, and the account button; one ghost-button hover treatment across all three nav controls; every header glyph moved onto the Lucide sprite (including the archive.org avatar photo, dropping a third-party image request per logged-in page load). Nets 16px of vertical chrome back to content on every page (the row height itself is bound by the 47px logo, not the controls).@axe-core/playwrightinto the e2e suite (A11y, implements A11y Phase 2: Playwright + axe-core integration for page-level WCAG testing #13007).ol-drawerfor the hamburger menu (Foundation → components). Rebuilt on native<dialog>.showModal(), so the drawer gets the top layer, background inerting and Escape from the platform — the stacking-context problem solved without a z-index. Also surfaces Sign Up in the header below 480px, where the auth links were hidden entirely.Recently landed: #13228 (search results toolbar onto web components, adding
<ol-menu-popover>and the Details/Grid segmented control), #13238 (ol-paginationsizevariants), #13102 (color tokens), #13247 (z-index tokens), #13006 (jest-axe CI gate).Workstream 1 — Foundation
Design tokens, the
ol-*web component family, and living docs. This is the part everything else depends on.Tokens (absorbed from #11555)
Two-tier structure — primitives (a small curated set of raw values) → semantic tokens (design intent, e.g.
--radius-card,--color-surface-raised). Semantic tokens are what templates and components reference, so a category-wide change is a one-line edit. Tokens live instatic/css/tokens/, aggregated bystatic/css/tokens.css.Create tokens for…
Apply tokens across existing CSS…
z-index:values remain instatic/cssor in the Lit components; stylelint now enforces it)static/css; what's left is the deprecated aliases (in flight as Unify the site's colors on one warm palette and remove the old color names #13338) and 42 raw hexes inside Lit shadow DOM (see components, below)font-sizevalues vs. 194 tokenizedRemaining foundation work:
ol-*web components20 Lit components exist today in
openlibrary/components/lit/—ol-button,ol-dialog,ol-popover,ol-select-popover,ol-options-popover,ol-menu-popover,ol-segmented-control,ol-toggle,ol-chip/ol-chip-group,ol-toast/ol-toast-region,ol-banner,ol-tooltip,ol-carousel,ol-pagination,ol-scorecard,ol-read-more,ol-markdown-editor, and others. The family is real; what's missing is consistency guarantees across it.csstemplate literals.lint:cssglobs onlystatic/**/*.cssandopenlibrary/**/*.css, so styles authored inside.jsare unlinted — which is why 42 raw hexes survive in shadow DOM. Until this exists, "no new raw hex" is unenforceable there. Drafted in Stylelint coverage for Lit + Vue; retire raw hex from web components #13110 (closed).font-sizeto stylelint'sdeclaration-strict-valuelist once a type scale lands — currently the only success criterion with no enforcement pathol-*component (js/carousel/Carousel.js,static/css/lib/slick*.css) — blocked on Rebuild ol-carousel on native scroll snapping #13220ol-*componentLiving docs
The design system docs live at
/developers/design, assembled byopenlibrary/plugins/openlibrary/design.pyfrom 19 component pages underopenlibrary/templates/design/components/, with tokens parsed straight out of the token CSS.ol-icon's page is outstanding, in Add unified icon system #12955)Workstream 2 — A11y audit
A systematic WCAG 2.1 AA sweep, building on fixes already shipped rather than starting cold. Recently landed: search-modal dialog naming + arrow-key result nav + live region (52da434),
ReadButtondropdown summary name (#13116), generic-dropper arrow button label (#13029),OlSelectPopoverrole=group(#13037), iframe titles (#13012), decorative star ratings hidden (#13028), several contrast fixes (#13030, #13027), and the interactive-element audits for the book page (#4906) and Want To Read (#4911).langon non-English passages — Passages of Text Must Have Their Language Explicitly Declared #5015The CI gate matters more than any individual fix: without it, this list regenerates itself every 18 months. jest-axe covers component ARIA patterns; #13251 extends that to whole pages.
Workstream 3 — Mobile pass
Touch behavior, keyboard handling, and layout at small sizes. Extends the shipped search-modal mobile work (full-height modal, equal-height filter row, text-entry auto-zoom rules) and carousel touch gestures (#13101).
docs/ai/design.md; needs enforcement)Measurement
Core Vitals are the outside check on whether this is working, since "looks maintained" isn't measurable on its own:
Nothing measures these today — there's no vitals instrumentation in the repo yet. #13252 (read-only Matomo client) and #13213 (Core Vitals Retention Score) are the proposed reporting path. This ties into item 2 (Metrics/Core Vitals) of #13194.
Related
Stakeholders
@lokesh @cdrini @mekarpeles @jimchamp @RayBB
Instructions for Contributors