internetarchive / internetarchive/openlibrary
Epic: UI Modernization — consistency, mobile, accessibility
- Dominant language
- Python
- Stars
- 6.7k
- Forks
- 2k
- Avg merge
- 2d 19h
- Merged PRs (30d)
- 138
Description
## 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
- 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:
- **#12955** — Unified icon system: ``, `$: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.
- **#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 #13101. **Currently conflicts with master** — the docs page it patches moved to `templates/design/components/carousel.html.jinja`.
- **#13338** — Unify the site's colors on one warm palette and remove the old color names *(Foundation → tokens)*. The tail of #13102, in two halves: bring the accent colors, card/table lines, shadows, chips, and chart hues into the same warm family as the greys, then move the remaining ~370 call sites onto purpose-based names and delete the 16 legacy aliases that no longer have users. Absorbs the former #13284, which was closed and folded in so both halves ship as one reviewable change to the same files.
- **#13104** / **#12772** — Two competing takes on font-size and font-weight tokens. These need reconciling before either lands; the type scale is the largest remaining token gap.
- **#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).
- **#13251** — Wire `@axe-core/playwright` into the e2e suite *(A11y, implements #13007)*.
- **#13213** — Core Vitals Retention Score with an on-demand trigger *(Measurement)*, building on the read-only Matomo client in #13252.
- [ ] **#12326** — `ol-drawer` for the hamburger menu *(Foundation → components)*. Rebuilt on native `.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 `` and the Details/Grid segmented control), **#13238** (`ol-pagination` `size` 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.
### 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 in `static/css/tokens/`, aggregated by `static/css/tokens.css`.
Create tokens for…
- [x] colors — #13102
- [x] font families — #11540
- [ ] font sizes and weights — #13104 / #12772 (reconcile)
- [x] line heights — #11619
- [x] border radius — #11557
- [x] spacing — #12116
- [x] borders — #11602
- [x] z-index — #12363
- [x] breakpoints
- [x] control heights
- [ ] icon sizes — in #12955
Apply tokens across existing CSS…
- [x] font families — #11556
- [x] line heights — #11669
- [x] border radius — #11596
- [x] spacing
- [x] z-index — #13247 (zero raw `z-index:` values remain in `static/css` or in the Lit components; stylelint now enforces it)
- [ ] colors — all but 4 raw hex values are gone from `static/css`; what's left is the deprecated aliases (in flight as #13338) and 42 raw hexes inside Lit shadow DOM (see components, below)
- [ ] font sizes and weights — 280 literal `font-size` values vs. 194 tokenized
- [ ] borders — 199 literal border widths vs. 41 tokenized
Remaining foundation work:
- [ ] Self-hosted web fonts (variable, latin subset) — Literata + Schibsted Grotesk. Needs a fresh PR; #13103 (Literata + Inter) was closed unmerged before the typeface decision settled.
- [ ] Foundation surfaces: canvas, header chrome, semantic buttons/forms — #13105 (closed, needs re-landing on current tokens)
- [ ] Make font size consistent throughout the site — #8692
- [ ] Standardize subject tags UX using BEM — #11728
### `ol-*` web components
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 #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
- [ ] Retire slick, the last jQuery widget duplicating an `ol-*` component (`js/carousel/Carousel.js`, `static/css/lib/slick*.css`) — blocked on #13220
- [ ] 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.
- [x] A docs page for every shipped component (only `ol-icon`'s page is outstanding, in #12955)
- [x] 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 (52da434c3), `ReadButton` dropdown summary name (#13116), generic-dropper arrow button label (#13029), `OlSelectPopover` `role=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
- [x] Automated a11y CI gate using jest-axe — #13005, shipped in #13006
- [ ] Playwright + axe-core for page-level testing — #13007, in flight as #13251
- [ ] Contrast sweep against the new color tokens — #9633
- [ ] Accessible names for ARIA input fields — #9631; discernible link names — #9630
- [ ] Visible, consistent keyboard focus indicator — #7857
- [ ] Skip links to bypass navigation blocks — #5012
- [ ] Book page accessibility score — #9618
- [ ] Explicit `lang` on non-English passages — #5015
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
- [ ] Mobile search results density — chrome takes 62% of the viewport, 0 fully readable results above the fold — #13482
- [ ] Touch target sizing meets 44×44 minimum on interactive controls
- [ ] All text-entry controls sized to prevent iOS auto-zoom (rule documented in `docs/ai/design.md`; needs enforcement)
- [ ] Virtual keyboard handling — inputs stay visible when the keyboard opens; dialogs don't fight it
- [ ] Touch/pointer parity for components that assume hover (tooltip, popover, dropper)
- [ ] Excessive top spacing / alignment on mobile My Books — #12095
- [ ] Reserve cover image space to prevent CLS in list-view results — #12825
---
## Measurement
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.
---
## Related
- #13135 — Epic: Subject page redesign (consumed this foundation; now closed)
- #12129 — Author Page UX Improvements
- #12858 — Weekly Prompt homepage widget
- #13158 — Genre Explorer
- #13194 — 2026 H2 Planning: Engineering Initiatives Recap
## Stakeholders
@lokesh @cdrini @mekarpeles @jimchamp @RayBB
---
### Instructions for Contributors
* **Before** [creating a new branch](https://github.com/internetarchive/openlibrary/wiki/Git-Cheat-Sheet#making-changes-and-creating-a-pull-request) or pushing up changes to a PR, please first [run these commands](https://github.com/internetarchive/openlibrary/wiki/Git-Cheat-Sheet#working-on-your-branch) to ensure your repository is up to date, as the pre-commit bot may add commits to your PRs upstream.
Contributor guide
Research direction
This is a broad epic spanning static/css/tokens/, openlibrary/components/lit/, openlibrary/templates/design/components/, and the design plugin at openlibrary/plugins/openlibrary/design.py. Start by selecting a specific unchecked item and reading its linked issue or existing pull request first. Done means completing that scoped item and meeting its listed test, accessibility, token, mobile, or documentation criterion.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- css, javascript, python
- Domain
- accessibility, documentation, frontend, performance, testing
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100