---
title: "S17 Library · Library"
description: "Contract, a board per state (loading, list, language-en, filtered, filter-empty, error, empty, offline), tokens and strings."
source: "design/hifi/S17/screen.md"
---

> Part of [Brand and design](/design/llms.txt). Every page: [llms.txt](/llms.txt).

# Hi-fi: S17 Library · Library

> Generated by `botgumi-design index` from `../../screens.json`. Edit that file, not this page.

Stage: **hifi** · Key: `library/library` · Data-binding map: [bindings.md](/design/hifi/s17/bindings/index.md)

Drawn in the gallery: [Library](/design/gallery/index.html#g-library). Each board is the committed HTML in `screens/`, drawn on the example data in `docs/api/examples/`.

## Contract

- **loading**: Skeleton rows. No import button in v1 (config.flags.import_enabled = false).
- **list**: All languages + topics: unseen / in progress / completed (best score) / locked tier. A language tag shows on each row when more than one practice language exists. No import button in v1 (config.flags.import_enabled = false).
- **language-en**: Language filter = English (GET /v1/articles?language=en). No import button in v1 (config.flags.import_enabled = false).
- **filtered**: Topic filter = Product. No import button in v1 (config.flags.import_enabled = false).
- **filter-empty**: Topic with 0 articles → empty state + clear filter. No import button in v1 (config.flags.import_enabled = false).
- **error**: Retryable load failure. No import button in v1 (config.flags.import_enabled = false).

Flows: F3 Free practice from the library.

Reads and calls:

- `GET /v1/topics (Accept-Language)`
- `GET /v1/articles?language=&topic=&status=&cursor=`
- `POST /v1/sessions {article_id, kind: free}`

Local state:

- `local:library.filter.language`
- `local:library.filter.topic`
- `local:library.filter.status`

## Boards

One row per state. A state without boards of its own says what shows it.

| State | Board | Example data |
|---|---|---|
| loading | [zh-Hans](/design/gallery/library/library--loading.html) · [en](/design/gallery/library/library--loading.en.html) |  |
| list | [zh-Hans](/design/gallery/library/library--list.html) · [en](/design/gallery/library/library--list.en.html) | articles.json ([zh-Hans](/design/examples/zh-Hans/articles.json) · [en](/design/examples/en/articles.json)) |
| language-en | [zh-Hans](/design/gallery/library/library--language-en.html) | articles.json ([zh-Hans](/design/examples/zh-Hans/articles.json)) |
| filtered | [zh-Hans](/design/gallery/library/library--filtered.html) · [en](/design/gallery/library/library--filtered.en.html) | articles.json ([zh-Hans](/design/examples/zh-Hans/articles.json) · [en](/design/examples/en/articles.json)) |
| filter-empty | [zh-Hans](/design/gallery/library/library--filter-empty.html) · [en](/design/gallery/library/library--filter-empty.en.html) |  |
| error | [zh-Hans](/design/gallery/library/library--error.html) · [en](/design/gallery/library/library--error.en.html) |  |
| empty | [no board of its own](/design/hifi/s17/bindings/index.md#states): From [states.md](/design/states/index.md) (Library): [library--filter-empty](/design/gallery/library/library--filter-empty.html) |  |
| offline | [no board of its own](/design/hifi/s17/bindings/index.md#states): From [states.md](/design/states/index.md) (Library): cached first page + banner; filters on cached data only |  |

### The boards

Each state as drawn (393 × 852), one board per locale. Open a board on its own for its spec panel.

**loading**

[S17 loading · zh-Hans](/design/gallery/library/library--loading.html) [S17 loading · en](/design/gallery/library/library--loading.en.html)

**list**

[S17 list · zh-Hans](/design/gallery/library/library--list.html) [S17 list · en](/design/gallery/library/library--list.en.html)

**language-en**

[S17 language-en · zh-Hans](/design/gallery/library/library--language-en.html)

**filtered**

[S17 filtered · zh-Hans](/design/gallery/library/library--filtered.html) [S17 filtered · en](/design/gallery/library/library--filtered.en.html)

**filter-empty**

[S17 filter-empty · zh-Hans](/design/gallery/library/library--filter-empty.html) [S17 filter-empty · en](/design/gallery/library/library--filter-empty.en.html)

**error**

[S17 error · zh-Hans](/design/gallery/library/library--error.html) [S17 error · en](/design/gallery/library/library--error.en.html)

## Tokens used

By path in `../../system/tokens.source.json`.

| Token | Where |
|---|---|
| `color.accentPrimary` | `.btn--primary` |
| `color.accentPrimaryPressed` | `.btn--primary` |
| `color.bgCanvas` | `.device`, `.large`, `.navbar`, `.screen` … |
| `color.bgInverse` | `.chip`, `.tag--ink` |
| `color.bgInverseRaised` | `.skel` |
| `color.bgSunken` | `.btn`, `.segmented`, `.skel`, `.state-illo` … |
| `color.bgSurface` | `.btn--secondary`, `.chip`, `.list`, `.segmented` |
| `color.lineHairline` | `.btn--secondary`, `.btn--sm`, `.chip`, `.list-row` … |
| `color.lineInverse` | `.topic-ink` |
| `color.lineStrong` | `.btn--secondary`, `.tag--line` |
| `color.textDisabled` | `.btn` |
| `color.textOnAccent` | `.btn--primary` |
| `color.textOnInverse` | `.chip`, `.tag--ink` |
| `color.textOnInverseSecondary` | `.chip`, `.count` |
| `color.textPrimary` | `.btn--secondary`, `.chip`, `.device`, `.home-indicator` … |
| `color.textSecondary` | `.c-secondary`, `.segmented`, `.state-illo`, `.tag` |
| `color.textTertiary` | `.c-tertiary`, `.chip`, `.count`, `.list-row` … |
| `color.topicCoral` | `.topic-coral` |
| `color.topicGreen` | `.tag--green`, `.topic-green` |
| `color.topicGreenSoft` | `.tag--green` |
| `color.topicViolet` | `.tag--violet`, `.topic-violet` |
| `color.topicVioletSoft` | `.tag--violet` |
| `radius.button` | `.btn` |
| `radius.md` | `.list` |
| `radius.pill` | `.btn--sm`, `.chip` |
| `radius.sm` | `.topic-tile` |
| `radius.xs` | `.tag` |
| `space.gutter` | `.chip-row`, `.large`, `.navbar`, `.pad` |
| `space.s6` | `.has-tabbar`, `.screen` |
| `space.safeBottom` | `.has-tabbar`, `.screen`, `.tabbar` |
| `space.safeTop` | `.device`, `.screen` |
| `space.sizeCtaHeight` | `.btn`, `.screen` |
| `space.sizeNavbarHeight` | `.navbar` |
| `space.sizeTabbarHeight` | `.has-tabbar`, `.screen`, `.tabbar` |
| `typography.callout` | `.t-callout` |
| `typography.caption1` | `.t-caption` |
| `typography.footnote` | `.t-footnote` |
| `typography.largeTitle` | `.t-large-title` |
| `typography.subheadline` | `.t-subhead` |
| `typography.title3` | `.t-title3` |

## Strings

`app.name`, `band.clear`, `common.all`, `common.difficulty`, `common.error_code_retryable`, `common.retry`, `common.today`, `growth.title`, `home.level.a11y`, `lang.all`, `lang.en`, `lang.zh-Hans`, `library.empty.body`, `library.empty.cta`, `library.empty.title`, `library.error.body`, `library.error.title`, `library.filter.language.a11y`, `library.filter.status.a11y`, `library.filter.status.completed`, `library.filter.status.unseen`, `library.filter.topic.a11y`, `library.footer.tier3`, `library.row.locked`, `library.row.status.best`, `library.row.status.in_progress`, `library.row.status.unseen`, `library.subtitle`, `library.title`, `notif.daily.title`, `paywall.calendar.legend.today`, `paywall.calendar.rest`, `paywall.projection.score`, `processing.unreliable.reason_label`, `read.tag.daily`, `tab.growth`, `tab.home`, `tab.library`, `tab.nav.a11y`

## Notes for the app tracks

Motion, haptics, Dynamic Type and VoiceOver: [engagement.md](/design/engagement/index.md), [tokens.md](/design/tokens/index.md) §6–7 and
[ios-handoff.md](/design/ios-handoff/index.md). Open a board with `?theme=dark&dt=1.35&binds=1` to see dark mode, the largest
tested type size and every bound element.

Tracking-plan events: no tracking plan page exists yet; ADR-001 D12 names the events.

Tokens with no group in `system/tokens.source.json` yet (they stay in [tokens.css](/design/tokens.css)): `--dt`, `--elevation-1`, `--font-family-reading`, `--font-family-text`, `--font-weight-bold`, `--font-weight-medium`, `--font-weight-regular`, `--font-weight-semibold`, `--motion-duration-fast`, `--motion-duration-instant`, `--motion-ease-standard`, `--type-tracking-tight`.
