UX (reader experience)
Claude skill atlas-ux · for Designer, Frontend, Editor
How readers move through Atlas of Today's World and what every public screen must give them - globe, panel and full-page navigation, long-form topics with subtopics and timelines, answer-first blocks, loading/empty/error states, support and volunteer calls to action, sharing, URL state and phone behaviour. Use when designing or changing a public page, flow, panel, menu, empty state, call to action, share or filter, or anything a reader clicks.
UX: the reader experience
The reader never gets lost. Every screen answers first, keeps its state in the URL and offers a next step. Build new screens from the existing levels, blocks and states below instead of inventing new ones.
Before you start
- Look at the real thing on a phone and a laptop: https://atlas-of-todays-world.vercel.app
- Related skills:
atlas-visual-design(look and tokens),atlas-accessibility(keyboard, focus, contrast, screen readers),atlas-content(wording, sources, images),atlas-maps-data(globe layers, charts, legends),atlas-performance(loading budgets),atlas-seo(titles, URLs),atlas-frontend(how to build it). - Public UI text lives in
src/messages/en.json(English only). Editors override many strings in Admin → UI texts, so never hard-code reader-facing text in a component.
Rules
Three levels, one way in and out
| Level | Pages | Built with |
|---|---|---|
| Globe | / (home): satellite globe, layer switcher, search | src/app/[locale]/(map)/page.tsx, src/components/map/* |
| Panel next to the globe | country, region, news item, data layer (/view/…) | ContentRail; right column from 768 px (DESKTOP_MIN_PX), bottom sheet below |
| Full page over the globe | Topics, a topic, Global issues, a global issue | FullPage; the globe waits in a corner window (MiniGlobeLink) |
- Put a new page type into one of these levels.
railKind()andisFullPage()insrc/config/layout.tsdecide which; extend their lists, never add a one-off check in a page. - Closing a panel (X or Esc) returns to the globe (
ContentRailcloseHref="/"). Going one level up is the job of the breadcrumbs (Breadcrumbs: location trail, current page last and unlinked), not of the close button. - The globe is never the only way to a place.
/countries(routes.countries) lists every place; the home page's hidden skip link andGlobeUnavailable(no WebGL) both point there. A new kind of place must also appear in a plain list. - A data layer on the globe always comes with its ranked HTML list (
/view/[indicator]). - Navigation is a real link (
Linkfrom@/components/i18n/Linkon public pages), so Cmd/Ctrl-click, middle-click and "copy link" work. Never navigate withonClick+router.pushwhen a link would do. - On touch the first tap on a place previews it and the second opens it
(
components/map/globe/tapPreview.ts). Keep that contract for anything new on the globe.
Long-form reading: topics, global issues, portraits
A topic page (features/topics/components/EncyclopediaArticle.tsx) has this order. Keep it
for every long-form type:
- Hero: photo or colour wash, breadcrumbs, title, summary, Share (
ArticleHero). - Meta line: places, reading time. Author, published and updated dates.
- In short: the answer in a few sentences (
seo.geoSummary, max 800 chars). - Key points: up to 5 bullets (
summaryPoints; the schema allows 5). - Intro text, then the timeline (
Timeline), then Maps & charts (VisualsCarousel). - Subtopic cards with the open subtopic below (
DossierExplorer), then Learn more tiles. - FAQ (
FaqList), author bio, end links (ArticleEnd).
- Answer first: the In short block and the key points come before any long text. Each
subtopic opens with its own summary points. Wording rules are in
atlas-content; the In short text is also the GEO summary (atlas-geo). - A block with no data is not rendered at all (no timeline, no visuals, no FAQ → nothing, no empty heading). The one deliberate exception is the zero-topics invitation below.
- Subtopics have stable anchors
#topic-N(routes.topic(slug, "topic-N")). Opening one writes the hash (OpenOnHash), so it can be shared and reloaded. Never change the anchor scheme. - Show published and updated dates as
<time dateTime>; show "Updated" only when it differs. Subtopics show their own updated date. - Reading aids are
ReadingProgress(sticky under the header,UNDER_HEADER_BAR) and, on phones,TopicJumpBar. Do not add another sticky bar. - Timelines run oldest to newest in one
Rail(limits:atlas-content). - Optional depth goes into
<details>(Accordion), which works without JavaScript. Never put the main answer inside a disclosure.
States: loading, empty, error, not found
| Situation | Use |
|---|---|
| Slug route loading | loading.tsx with RailSkeleton (panel) or FullPageSkeleton (full page) |
| Empty list or "no results" | EmptyNote: say what is empty or what was searched, offer a next step |
| Place with zero topics | NoTopicsCta / TopicsInvite: red invitation to write (volunteerHref(place)) |
| Error inside a route | error.tsx rendering RouteError (on map pages the globe stays) |
| Not found | not-found.tsx rendering ErrorState (plus the country outline quiz) |
| Globe cannot start | GlobeUnavailable with a link to /countries |
- No indicator for waits under about 1 s. A skeleton must match the final layout; never ship a frame with an empty middle.
- Design every new view for four data states: empty, one item, typical, very long (long country
names, 12 subtopics
MAX_CHAPTERS, 24 tilesMAX_TILES). - Show "no results" only after loading has finished.
- Every error and 404 screen offers a way back or forward (
ErrorStateaction/backLabel).
Support, patrons and volunteers
- Owner decisions, keep them: the gold coin animation (
DonateCoin), the spark ring on "See all N topics" (spark-ring), the "Support the Atlas" floating button and menu item. The globe's badges carry no red (owner, 2026-10-16): a 0 is the same white pill as any count. FloatingActions(Newsletter + Support the Atlas) is the only floating call to action. It is on every public page except the admin and the donation pages. Do not add a second one.- Keep support separate from editorial text:
PatronsCalloutand donation cards sit after the content or in their own box, never inside an article body. - Button labels name the outcome: "Become a patron", "Write a topic", "Join as volunteer". Never "Continue", "Submit" or "Click here".
- Invitations prefill what is known:
volunteerHref(place)opens the volunteer form with the place filled in. Never ask again for something the reader already gave in the same flow. - Readers support without an account. Payments stay in Stripe test mode.
Sharing
- One component:
ShareButton(components/atlas/ShareButton.tsx). Phones get the system share sheet, other devices copy the link and see an announced "Link copied". - Pass
hashto share one subtopic;tone="dark"over photos. - No Share on secret preview links:
EncyclopediaArticleshows it only when the public page passesshare.
URL state and Back
- Everything a reader would share or return to lives in the URL: filters and search on index
pages (
useUrlParams/setUrlParamsincomponents/atlas/url-filters.ts), the open subtopic (#topic-N), the search query (routes.searchFor(q)). - Static index pages render the full list on the server and narrow it in the browser. Do not read
searchParamsin their server render; that makes the route dynamic (atlas-architecture). - Filter tweaks use
replaceState(no history spam); moving to another page is a real navigation. - Back returns to the same scroll position. Every new scrolling container uses
useScrollMemoryorRememberedScroll.
Phones
- The panel is a bottom sheet at 58 % of the height (
MOBILE_SHEET); the place stays visible above it, and the handle expands it. Floating buttons and the cookie notice step aside for it; never pin another fixed element over the sheet. - No sideways page scroll at 320, 375 and 1280 px (
tests/overflow.spec.ts). Carousels and tables may scroll inside their own box. - Nothing is hover-only: every hover hint has a tap equivalent.
- Touch target sizes and gestures: see
atlas-accessibility.
How we do it in Atlas
// A place with no topics invites the reader to write the first one.
<NoTopicsCta count={topicCount} place={place.name} />
// Share one subtopic of a topic page.
<ShareButton title={chapter.title} hash={`topic-${index + 1}`} />
// Index page filter kept in the URL (client component).
const params = useUrlParams();
setUrlParams({ category: next || null });Checklist
- [ ] The page sits in one of the three levels; close, Esc and breadcrumbs lead somewhere sensible.
- [ ] Long-form content opens with In short and key points; empty blocks are not rendered.
- [ ] Loading, empty, error and very-long states exist and were checked.
- [ ] Shareable state (filters, open subtopic, query) is in the URL; Back restores scroll.
- [ ] Calls to action name the outcome; nothing is added beside
FloatingActions. - [ ] Checked at 320 px and on a touch device; nothing depends on hover.
- [ ] Reader-facing text comes from
en.jsonviagetT()/useMessages().
Anti-patterns
- A new modal or a fourth page level for content that fits a panel or a full page: readers lose the globe and the Back button.
- A spinner on a whole page, or a skeleton that does not match the page: it reads as broken.
- An empty heading ("Timeline" with nothing under it): it promises content that is not there.
- Filters kept only in React state: the view cannot be shared and Back loses it.
- A second floating donate button or a donation box inside the article text: it competes with the content and erodes trust.
- Removing the coin or the spark to "calm the page": these are owner decisions.
Sources
Full list with notes: reference.
- NN/g, F-shaped reading and front-loading: https://www.nngroup.com/articles/f-shaped-pattern-reading-web-content/
- NN/g, skeleton screens: https://www.nngroup.com/articles/skeleton-screens/
- NN/g, breadcrumbs: https://www.nngroup.com/articles/breadcrumbs/
- Vercel Web Interface Guidelines (URL state, links, states): https://vercel.com/design/guidelines
- Our World in Data topic page (key insights, dates): https://ourworldindata.org/co2-and-greenhouse-gas-emissions
- WCAG 2.2 new criteria (redundant entry): https://www.w3.org/WAI/standards-guidelines/wcag/new-in-22/
Files of this skill
reference.md
atlas-ux: reference
Longer notes and sources behind the rules in SKILL.md. Paraphrased; nothing copied.
Where each reader flow lives in the code
| Flow | Files |
|---|---|
| Home globe | src/app/[locale]/(map)/page.tsx, src/components/map/AtlasGlobe.tsx, MapControls, ViewSwitcher, IssueMenu, FeaturedSubtopics |
| Panel (country, region, news, data layer) | src/components/ContentRail.tsx, src/components/portrait/Portrait.tsx, src/components/CountryCard.tsx, src/app/[locale]/(map)/view/[indicator]/page.tsx |
| Full page (topics, global issues) | src/components/FullPage.tsx, src/features/topics/components/{TopicsBrowser,EncyclopediaArticle,DossierExplorer}.tsx, src/features/portraits/components/{IssuesBrowser,IssueArticle}.tsx |
| Plain list of every place | src/app/[locale]/(pages)/countries/page.tsx |
| Loading, error, not found | loading.tsx per slug route, error.tsx per route group, not-found.tsx (with features/quiz) |
| Support and volunteers | src/components/membership/{FloatingActions,DonateCoin,DonationCard}.tsx, src/components/topics/{NoTopicsCta,TopicsInvite}.tsx, PatronsCallout in src/components/portrait/sections.tsx, /membership |
| Scroll memory | src/lib/use-scroll-memory.ts, src/components/RememberedScroll.tsx |
| URL filters | src/components/atlas/url-filters.ts |
Review questions for a new public screen
- Which level is it (globe, panel, full page)? Why not one of the existing pages?
- What does a reader learn in the first screenful? Is that the answer or a preamble?
- What happens with no data, one item, and 50 items with long names?
- What can be shared? Does the URL carry it? Does Back return to the same spot?
- What is the next step at the end of the page and on its error state?
- Does it work with one thumb on a 320 px phone, with the bottom sheet open?
- Is any reader-facing string hard-coded instead of coming from
en.json?
Patterns borrowed from exemplary sites
- Our World in Data: a key-insights list at the top that doubles as navigation; published and updated dates near the title; view state (tab, countries) in the query string. https://ourworldindata.org/co2-and-greenhouse-gas-emissions , https://ourworldindata.org/grapher/life-expectancy
- Wikipedia: a short description under the title; collapsible sections on phones. https://www.mediawiki.org/wiki/Reading/Web/Desktop_Improvements/Features/Visual_Refinements
- The Pudding: support and newsletter cards set apart from stories; "Load more" instead of infinite scroll. https://pudding.cool/
- GOV.UK browse: topic cards as a linked heading plus a one-line description of what is inside. https://www.gov.uk/browse/working
Sources
- NN/g, F-shaped pattern and front-loading: https://www.nngroup.com/articles/f-shaped-pattern-reading-web-content/
- NN/g, tables of contents: https://www.nngroup.com/articles/table-of-contents/
- NN/g, breadcrumbs: https://www.nngroup.com/articles/breadcrumbs/
- NN/g, skeleton screens: https://www.nngroup.com/articles/skeleton-screens/
- NN/g, response time limits: https://www.nngroup.com/articles/response-times-3-important-limits/
- NN/g, empty states: https://www.nngroup.com/articles/empty-state-interface-design/
- NN/g, ten usability heuristics: https://www.nngroup.com/articles/ten-usability-heuristics/
- Laws of UX (Jakob, Hick, Doherty): https://lawsofux.com/
- Vercel Web Interface Guidelines: https://vercel.com/design/guidelines
- WCAG 2.2, what is new (redundant entry, dragging): https://www.w3.org/WAI/standards-guidelines/wcag/new-in-22/
- GOV.UK clear language: https://guidance.publishing.service.gov.uk/writing-to-gov-uk-standards/writing-guidelines/clear-language/