Skip to content

Components

Karthik P edited this page Oct 27, 2026 · 2 revisions

This page lists every UI component on the website: its purpose, what it is made of, the tokens it uses, its states, and whether Fumadocs UI provides it or it is written for this site. Token values are on Design Tokens; this page only names them.

Fumadocs names and props were checked against fumadocs-ui 16.17.8 (current on 2026-10-24) and its docs.

Overview

Fumadocs UI colours its components through --color-fd-* variables, mapped once on Design Tokens (--color-fd-primary is ink and --color-fd-accent a hover grey, not amber). Where a radius here differs from Fumadocs', override it as on Shape Elevation and Motion. Every interactive element shows the global focus outline (Accessibility), and every transition stops under prefers-reduced-motion.


Header

Purpose. Gets the reader to the docs, search and the repository from any page.

Source. Fumadocs UI. baseOptions() in lib/layout.shared.tsx is passed to both the Notebook DocsLayout (with nav: { mode: 'top' }) and HomeLayout: nav.title (the logo lockup), links and githubUrl (Fumadocs: navbar, links).

Anatomy. Logo lockup linking to /; search trigger; nav links (Docs Site Structure has the list); GitHub link; theme toggle.

Tokens. Height 64 px, sticky; background bg; bottom border 1px rule; links 14 px weight 500 text-2; gutters as on Layout and Spacing.

States. Link hover: text. Current section: text, weight 500, no amber. Below 1024 px the text links and the GitHub link move into the sidebar drawer (docs) or Fumadocs' menu (landing); below 768 px the search field becomes an icon and the theme toggle moves into the drawer. The logo shows the mark only below 96 px of available width (Brand).

Do. Keep the nav to four links or fewer. Show the GitHub mark only as the link to the repository, unmodified. Don't. Add a version switcher before there are versions, a star count (a request to GitHub on every page view), or third-party badges.

Sidebar

Purpose. Shows where the reader is in the docs and every page they can go to.

Source. Fumadocs UI Notebook layout. The tree comes from content/docs/ and each folder's meta.json (page conventions).

Anatomy. Section separators (Start, Concepts, Guides, Reference, More) as 13 px weight 600 text, not uppercase; page items; collapsible folders; external links with ExternalLink.

Tokens. Width 280 px on bg; items 14 px / 1.5 text-2, 6 × 12 px padding, radius 8; 24 px above each separator.

States. Hover: text on surface. Current page: text, weight 500, on surface, a 2px accent bar on its start edge (one of amber's five uses), aria-current="page". Below 768 px the sidebar is a drawer opened from the header.

Do. Use the page's short title; keep it under 28 characters so it fits one line. Don't. Add icons to items, or nest folders more than one level.

On this page

Purpose. Lists the page's H2 and H3 headings and shows which is in view.

Source. Fumadocs UI. DocsPage takes toc={page.data.toc}; the default tableOfContent style is a track line with a thumb (Docs Page). Below 1280 px Fumadocs shows it as a collapsed bar under the header.

Tokens. Column 220 px; label "On this page" 13 px weight 600 text; links 13 px / 1.5 text-2, H3 indented 12 px; track 1px rule; thumb text.

States. Active heading and hover: text. Never amber: it is not the current page.

Breadcrumb

Purpose. Shows the section a page belongs to, above the title.

Source. The DocsPage breadcrumb slot (includeRoot, includePage, includeSeparator).

Anatomy and tokens. Section › page, 13 px text-2, separator ChevronRight. Sections with an index page are links (hover text); the current page is plain text with aria-current="page". Not shown on top-level pages.

Search dialog

Purpose. Finds a page or heading by text, in the browser, with no search service.

Source. Fumadocs UI dialog with fumadocs-core's useStaticSearch, given the base-path URL of the index (Website Tech Stack). Opens with Ctrl K or ⌘ K.

Anatomy. Trigger: a field-shaped button with Search, "Search docs" and a key hint. Dialog: overlay, input with icon and close button, results (page title, then matching headings).

Tokens. Trigger 14 px text-2, 1px rule-strong border (its label identifies it, so it is not held to 3:1), radius 6, height 36 px. Dialog raised, radius 12, popover shadow, max width 640 px, 16 px from the top on phones. Overlay overlay. Matched words weight 600, not coloured.

State Shows
First open The index (one JSON file from the site) loads; "Loading…" in text-2
Results Arrow keys move the selection; selected row on surface
No results "No results for “{query}”." and a link to the FAQ
Index failed "Search could not load. Use the sidebar, or llms.txt."

Don't. Add a hosted search service; it would send queries to a third party.

Theme toggle

Purpose. Lets the reader override the OS light/dark setting.

Source. Fumadocs UI. RootProvider wraps the site in next-themes (defaultTheme: 'system', attribute: 'class'). The choice is stored in localStorage, not a cookie. Fumadocs' single-key "d" shortcut is off: theme={{ hotKey: false }} on RootProvider (fumadocs-ui 16.17.8, provider/base; Accessibility).

Tokens. Icons 16 px text-2; selected segment text on surface; radius full. The switch is instant (disableTransitionOnChange).

Proposed: mode="light-dark-system" (three segments) instead of the default two-state button, so a reader can return to following the OS, with Monitor for "system" instead of Fumadocs' Airplay.

Code tabs

Purpose. Shows one example in each language and remembers the reader's language across the site.

Source. Fumadocs UI Tabs with groupId and persist (Tabs): every set with the same groupId switches together, and persist keeps the choice in localStorage.

<Tabs groupId="lang" persist items={['Rust', 'Python', 'JavaScript']}>
<Tab value="Rust">

```rust
let due = support.add(&opened, SignedDuration::from_hours(8))?;
```

</Tab>
<Tab value="Python">

```python
support.add(opened, hours=8)  # 2026-12-30 09:30, London time
```

</Tab>
<Tab value="JavaScript">

```js
support.add("2026-12-24T16:30:00Z", 8 * 3_600_000); // Date 2026-12-30T09:30:00.000Z
```

</Tab>
</Tabs>

Labels. Always Rust, Python, JavaScript, in that order, as text (no logos). Every Tab has an explicit value.

Tokens. Labels 14 px weight 500; inactive text-2; active text with a 2px accent underline (one of amber's five uses); tab list bottom border 1px rule; the frame is the code block's.

States. Hover text. Arrow keys move between tabs (Radix tabs).

Do. Show all three when the API exists in all three; mark a one-language sample with a badge instead of a one-tab set. End every tab at the same result. Don't. Use another groupId for language tabs, or put install commands in the language group.

Check in the first build that each tab stays a labelled fenced block in the page's .md version (Agent Experience).

Install command block

Purpose. The command to add workclock with each package manager, ready to copy.

Source. Custom, components/install-tabs.tsx, on Fumadocs Tabs with groupId="install" and persist. Fumadocs' npm code block only converts between npm, pnpm, yarn and bun.

Tab Command (README)
cargo cargo add workclock jiff
pip pip install workclock
uv uv add workclock
npm npm i workclock

Under the cargo tab, one line in text-2: "The Rust API takes and returns jiff 0.2 types, so Rust code depends on jiff too." Before 0.1.0, a Warning callout above the block: "In development. No release has been published yet."

States. Fumadocs' copy button (Clipboard, then Check). Proposed: a polite live region announcing "Copied".

Don't. Add a $ prompt, or package managers the README does not list.

Code block

Purpose. Shows code that runs as written.

Source. Fumadocs UI: fences highlighted at build time by Shiki through rehypeCode, rendered by the default MDX components with a copy button (Code Block). Themes github-light-high-contrast and github-dark-default (Color).

Tokens. surface; 1px rule border; radius 8; JetBrains Mono 13 px / 1.6; padding 16 px; optional title bar (title="…") 13 px text-2 with a rule bottom border. Highlighted lines use --color-fd-primary (ink), never amber. Diff lines use the --color-fd-diff-* values.

States. Long lines scroll inside the block; the page never scrolls sideways.

Do. Give every fence a language. Show results as comments in the README's form (Writing Style). Line numbers only for programs over 20 lines. Don't. Use screenshots of code, or ... in a sample meant to run.

Callouts

Purpose. Sets apart something the reader must not miss.

Source. Fumadocs UI Callout with type and title. Types in 16.17.8: info (default), warning, error, success, idea.

Site name type Colour Icon Use
Note info info Info Context the reader needs
Warning warning bar and icon accent, title accent-text (one of amber's five uses) TriangleAlert A wrong answer if ignored; the pre-release status line
Danger error danger CircleX Data loss or a hang. Rare.

success and idea are not used.

Anatomy and tokens. surface; 1px rule border; 3px start bar in the type colour; icon 16 px in the type colour; a title starting with the type word ("Note", "Warning", "Danger"), 14 px weight 600; body 16 px / 1.7 text; radius 6; padding 12 × 16 px. Always set title: Fumadocs' icon has no text, so the word is what a screen reader hears.

Do. At most one callout per screen. Start the body with the fact. Don't. Put tabs or another callout inside a callout.

API parameter table

Purpose. Lists parameters with types and defaults.

Source. Fumadocs UI TypeTable; fields take type and optionally description, default, required and others (Type Table).

<TypeTable
  type={{
    timeZone: {
      type: 'string',
      description: 'IANA time zone for rules without their own.',
      default: '"UTC"',
    },
    rules: {
      type: 'readonly Rule[]',
      description: 'The rules, in any order.',
      required: true,
    },
  }}
/>

Tokens. Names and types JetBrains Mono 13 px; descriptions 14 px text; row borders 1px rule; "required" as a word, not a colour.

Do. One table per language with that language's names (time_zone, timeZone); copy descriptions from the docstrings or TSDoc. Don't. Describe Rust items; the Rust reference is docs.rs.

Steps

Source. Fumadocs UI Steps, Step (Steps). For install-and-run sequences of three or more steps, not for lists of facts.

Tokens. Number circle 24 px, 1px rule-strong border, 13 px weight 600 text, tabular figures; joining rule rule.

Language cards

Purpose. Sends readers to the quickstart for their language.

Source. Fumadocs UI Cards, Card (title, description, href).

Card Line
Rust Rust 1.85 or later. Takes and returns jiff types.
Python CPython 3.9 or later. abi3 wheels, no Rust toolchain needed.
JavaScript Node.js 22 or later, bundlers and browsers. Types included.

Tokens. Three in a row from 768 px; surface; 1px rule border, rule-strong on hover; radius 8; padding 16 px; title 16 px weight 600; line 14 px text-2. No shadow, no lift, no logos.

Page actions

Purpose. Lets a reader take a page to their own tools (Agent Experience).

Source. Custom, components/page-actions.tsx. It reuses Fumadocs' MarkdownCopyButton (from fumadocs-ui/layouts/notebook/page) with the .md URL. It does not use Fumadocs' ViewOptionsPopover: in 16.17.8 that menu is fixed (GitHub, Markdown, Scira AI, ChatGPT, Claude, Cursor), shows each service's logo, and adds the base path only under Vite (layouts/shared/page-actions.js).

Anatomy. Under the page description, left-aligned:

  1. Copy page (Copy): copies the page's Markdown.
  2. Open (ChevronDown): a menu with View as Markdown (FileText), Open in Claude and Open in ChatGPT (ExternalLink). Text labels only.

The "Open in" items open a new tab with the prompt and link formats on Agent Experience.

Tokens. Buttons 13 px weight 500, height 32 px, 1px rule-strong border, radius 6, bg; menu raised, radius 8, popover shadow. No amber.

State Shows
Copying Disabled while the .md file is fetched
Copied Check and "Copied" for 2 s; a polite live region says "Page copied as Markdown"
Failed "Copy failed"; "View as Markdown" still works

Don't. Put anything but the page URL in the prompt, add services, or show third-party logos.

Previous and next

Source. The DocsPage footer slot; neighbours come from the page tree.

Tokens. Two cards ("Previous" with ChevronLeft, "Next" with ChevronRight), label 13 px text-2, title 14 px weight 500 text, 1px rule border (rule-strong on hover), radius 8. One card full width when there is one neighbour.

Feedback link

Purpose. Lets a reader report a wrong or unclear page in one click.

Source. Custom, components/feedback-link.tsx, at the end of every docs page above previous/next: "Report a problem with this page" and "Ask a question in Discussions", 14 px text-2, underlined.

Behaviour. The first link opens the "Docs problem" issue form, .github/ISSUE_TEMPLATE/docs.yml (label type:docs, a text field id: page), as https://github.com/Karthik-beta/workclock/issues/new?template=docs.yml&page=<URL-encoded page URL>. GitHub fills form fields from query parameters named after their ids, and returns 404 if a parameter needs a permission the visitor lacks, such as labels, so the link never sets labels (creating an issue from a URL query). The second links to the Discussions Q&A category.

Don't. Add a "Was this helpful?" widget; it needs a server or a third party.

Footer

Source. Custom, components/site-footer.tsx, in both layouts. Columns are on Landing Page.

Tokens. Top border 1px rule; 14 px text-2 links; column titles 13 px weight 600 text; 48 px padding top and bottom. No logos, newsletter form or social icons.

Badges

Purpose. Short inline labels: the languages an API exists in, compat, a version.

Source. Custom, components/badge.tsx, rendered at build time as text. Never an image from a badge service such as shields.io, which is a third-party request on every view.

Tokens. 12 px weight 500, tabular figures, text-2 on surface, 1px rule border, radius 4, padding 2 × 6 px. Examples: Rust, Python, JavaScript, compat, 0.1.0. Not coloured by meaning.

Status banner

Proposed: one line across the top of every page until 0.1.0 is published: "In development. No release has been published yet."

Source. Fumadocs UI Banner with no id (it cannot be closed and stores nothing) and the default variant (Banner). surface, 1px rule bottom border, 13 px text, centred, 36 px tall. Removed in the 0.1.0 release PR.

Accordion

Purpose. The FAQ page: one question per item.

Source. Fumadocs UI Accordions (type="single") and Accordion (title, id; the item opens when the URL has that hash) (Accordion). Question 16 px weight 500 text, chevron text-2, dividers rule. Give every item an id. Don't hide anything a reader needs to use the library correctly.

Files

Source. Fumadocs UI Files, Folder, File (Files). A folder tree in a guide: JetBrains Mono 13 px text, icons text-2, 1px rule border, radius 8. Rarely needed.

Skip link

Source. Custom, components/skip-link.tsx; Fumadocs has none. The first focusable element on every page, to #nd-page in the docs and #main-content on the landing page. Code and rules on Accessibility.


Week strip

The house diagram: a week of working hours drawn from what workclock computes. Working time is bars, the time an operation counts is a fill, the answer is one amber marker.

Source. Custom, components/week-strip/. Two variants share one renderer:

Variant Where Data
Static Docs pages Computed at build time, rendered to HTML; no JavaScript needed to see it
Interactive Landing hero, playground Computed in the browser by the workclock package (Website Tech Stack)

Periods come from nextPeriod, the due instant from add, counted time from the periods between start and due. Nothing is drawn from hand-typed numbers. It is HTML and CSS (a grid of rows, bars positioned by percentage), not a fixed-size SVG, so text keeps its real size when the strip narrows.

Anatomy

          08:00   10:00   12:00   14:00   16:00   18:00     ← hour axis
Thu 24    │       │       │       │      ●▓▓▓│              ← start dot, used fill
Fri 25    │       ╱╱╱╱╱╱╱╱ Holiday ╱╱╱╱╱╱╱╱  │              ← hatching
Sat–Sun   │       │       Weekend │       │                 ← label only
Mon 28    │       ╱╱╱╱╱╱╱╱ Holiday ╱╱╱╱╱╱╱╱  │
Tue 29    │   ▓▓▓▓▓▓▓▓▓▓▓▓░  ░▓▓▓▓▓▓▓▓▓▓▓▓▓▓│
Wed 30    │   ▓▓▓|  Due Wed 30 Dec, 09:30     │              ← due marker and label
          Working hours 09:00–17:00, break 12:00–13:00. Times in Europe/London.
Part Spec
Rows One per day, earliest at the top, from the start day to the due day. Weekend days with no working time fold into one row, "Sat–Sun". Proposed: at least 7 and at most 14 days.
Row labels Every row has a text label: the day ("Thu 24"; "Thu" below 640 px), in a 72 px left column (40 px below 640 px), 13 px text-2, tabular figures. Holiday and weekend rows also carry the word in the row.
Hour axis Top. One hour before the earliest working time shown to one hour after the latest. HH:MM labels, JetBrains Mono 12 px text-2, every 2 hours (every 4 below 640 px).
Grid 1px rule vertical lines at each labelled hour. Rows separated by 8 px; no horizontal lines.
work bars Each period of working time: fill work, row height 24 px, radius 4. A break is the gap between bars.
used fill The part of each period the operation counts: fill used, inset 6 px top and bottom, radius 4.
Holiday 45° hatching, 1.5 px rule-strong, 6 px apart, over the hours that would have been worked, and the word "Holiday", 12 px text-2.
Weekend No bars; the word "Weekend", 12 px text-2.
Start dot A 6 px used circle at the start instant. Proposed: a label "Opened 16:30" beside it.
Due marker A 2 px accent line at the due instant, 4 px beyond the row above and below, with a 6 px accent dot at its top. Line and dot have a 2 px bg outline so they stay visible on work and used (Color). One of amber's five uses.
Due label "Due Wed 30 Dec, 09:30", 12 px weight 600 accent-text, tabular figures; right of the marker, or left when within 160 px of the right edge. Required: the marker never relies on colour.
Caption 13 px text-2: the working hours and the zone; in docs, the scenario in one sentence.
Frame Interactive: surface, 1px rule border, radius 12, padding 24 (the demo frame). Static: no frame.

States

State Static Interactive
Default The page's scenario The README scenario: opened Thursday 24 December 2026 16:30, 8 hours, due Wednesday 30 December 09:30
Loading n/a The default state, prerendered and correct; controls disabled; "Loading workclock…" in text-2
Input changed n/a Bars, fill and label redraw at once; only the marker moves, 240 ms (Shape Elevation and Motion)
Due beyond the last day shown Not allowed in docs The strip ends with ChevronRight at the right edge; the result sentence gives the due time
NoWorkingTimeError n/a No marker; "No working time: pick at least one working day." in danger with an icon. Reachable only if working hours become editable.
WebAssembly failed to load n/a The prerendered default stays; controls stay disabled; "The live demo could not load. The example above is correct for the default inputs."

Controls and accessibility

Inputs are defined on Landing Page; Accessibility has the checklist.

  • Controls sit outside the drawing. The drawing has role="img", which makes its children presentational, so a control inside it would be lost. Holiday toggles are <button aria-pressed> elements in the row-label column, outside the role="img" element, named like "Friday 25 December: holiday".
  • The result is text. A sentence in a polite live region outside the drawing: "Due 09:30 on Wednesday 30 December 2026 (Europe/London)." Updated once per committed change. Proposed: 300 ms after the last keystroke.
  • The drawing has a text alternative. An aria-label built from the same data, for example: "Working days from Thursday 24 to Wednesday 30 December 2026, 09:00 to 17:00 with a break from 12:00 to 13:00. Friday 25 and Monday 28 are holidays. 8 working hours from Thursday 16:30 end on Wednesday 30 December at 09:30."
  • Nothing is told by colour alone: holidays have hatching and a word, the due instant a label, the start a dot.

Do. Use the week strip whenever a page explains working time, add, sub or between, with the same scenario as the page's code tabs. Don't. Draw a strip by hand, use amber for anything but the marker and its label, animate on scroll or load, or put controls inside the role="img" element.

Open items

  • Proposed: the three-state theme toggle with Monitor; the "Copied" live region on the install block; the status banner; the 7–14 day strip range; the start-dot label; the 300 ms debounce; a "Show as table" button under the strip revealing day, working hours and time counted; the compact one-row variant (Icons and Illustration).
  • Proposed, needs approval: AutoTypeTable from fumadocs-typescript for the JavaScript reference (Website Tech Stack).

Clone this wiki locally