Skip to content
All skills

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

LevelPagesBuilt with
Globe/ (home): satellite globe, layer switcher, searchsrc/app/[locale]/(map)/page.tsx, src/components/map/*
Panel next to the globecountry, region, news item, data layer (/view/…)ContentRail; right column from 768 px (DESKTOP_MIN_PX), bottom sheet below
Full page over the globeTopics, a topic, Global issues, a global issueFullPage; the globe waits in a corner window (MiniGlobeLink)
  • Put a new page type into one of these levels. railKind() and isFullPage() in src/config/layout.ts decide which; extend their lists, never add a one-off check in a page.
  • Closing a panel (X or Esc) returns to the globe (ContentRail closeHref="/"). 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 and GlobeUnavailable (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 (Link from @/components/i18n/Link on public pages), so Cmd/Ctrl-click, middle-click and "copy link" work. Never navigate with onClick + router.push when 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:

  1. Hero: photo or colour wash, breadcrumbs, title, summary, Share (ArticleHero).
  2. Meta line: places, reading time. Author, published and updated dates.
  3. In short: the answer in a few sentences (seo.geoSummary, max 800 chars).
  4. Key points: up to 5 bullets (summaryPoints; the schema allows 5).
  5. Intro text, then the timeline (Timeline), then Maps & charts (VisualsCarousel).
  6. Subtopic cards with the open subtopic below (DossierExplorer), then Learn more tiles.
  7. 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

SituationUse
Slug route loadingloading.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 topicsNoTopicsCta / TopicsInvite: red invitation to write (volunteerHref(place))
Error inside a routeerror.tsx rendering RouteError (on map pages the globe stays)
Not foundnot-found.tsx rendering ErrorState (plus the country outline quiz)
Globe cannot startGlobeUnavailable 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 tiles MAX_TILES).
  • Show "no results" only after loading has finished.
  • Every error and 404 screen offers a way back or forward (ErrorState action / 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: PatronsCallout and 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 hash to share one subtopic; tone="dark" over photos.
  • No Share on secret preview links: EncyclopediaArticle shows it only when the public page passes share.

URL state and Back

  • Everything a reader would share or return to lives in the URL: filters and search on index pages (useUrlParams / setUrlParams in components/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 searchParams in 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 useScrollMemory or RememberedScroll.

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.json via getT() / 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

FlowFiles
Home globesrc/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 placesrc/app/[locale]/(pages)/countries/page.tsx
Loading, error, not foundloading.tsx per slug route, error.tsx per route group, not-found.tsx (with features/quiz)
Support and volunteerssrc/components/membership/{FloatingActions,DonateCoin,DonationCard}.tsx, src/components/topics/{NoTopicsCta,TopicsInvite}.tsx, PatronsCallout in src/components/portrait/sections.tsx, /membership
Scroll memorysrc/lib/use-scroll-memory.ts, src/components/RememberedScroll.tsx
URL filterssrc/components/atlas/url-filters.ts

Review questions for a new public screen

  1. Which level is it (globe, panel, full page)? Why not one of the existing pages?
  2. What does a reader learn in the first screenful? Is that the answer or a preamble?
  3. What happens with no data, one item, and 50 items with long names?
  4. What can be shared? Does the URL carry it? Does Back return to the same spot?
  5. What is the next step at the end of the page and on its error state?
  6. Does it work with one thumb on a 320 px phone, with the bottom sheet open?
  7. 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/

This site uses only the cookies it needs to work — no advertising or tracking cookies. Visits are counted without cookies. Privacy