docpensieve 0.4.0-beta.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/bin/docpensieve.js +5 -2
  2. package/package.json +5 -5
  3. package/src/commands/build.js +9 -1
  4. package/src/commands/dev.js +9 -1
  5. package/src/commands/init.js +23 -3
  6. package/starter/01-guide/01-installation.md +2 -3
  7. package/starter/01-guide/02-first-site.md +7 -1
  8. package/starter/01-guide/03-writing-pages.md +4 -0
  9. package/starter/01-guide/04-versions.md +4 -0
  10. package/starter/01-guide/05-themes.mdx +47 -13
  11. package/starter/01-guide/07-migrate-from-0-3.md +125 -0
  12. package/starter/01-guide/08-navigation.mdx +138 -0
  13. package/starter/01-guide/09-when-it-breaks.md +176 -0
  14. package/starter/01-guide/10-languages.md +160 -0
  15. package/starter/02-components/01-card.mdx +2 -2
  16. package/starter/02-components/02-columns.mdx +3 -0
  17. package/starter/02-components/03-time-timer.mdx +4 -0
  18. package/starter/02-components/04-tooltip.mdx +40 -0
  19. package/starter/02-components/05-tree.mdx +4 -0
  20. package/starter/02-components/06-scroll-to-top.mdx +3 -0
  21. package/starter/02-components/07-skill.mdx +30 -0
  22. package/starter/02-components/08-logo-icon.mdx +24 -0
  23. package/starter/02-components/09-for-theme.mdx +3 -0
  24. package/starter/02-components/11-menu.mdx +129 -0
  25. package/starter/02-components/12-admonition.mdx +154 -0
  26. package/starter/03-reference/02-configuration.md +96 -31
  27. package/starter/03-reference/03-frontmatter.md +3 -0
  28. package/starter/03-reference/04-theme.md +80 -62
  29. package/starter/03-reference/05-api.md +169 -12
  30. package/starter/03-reference/index.mdx +3 -0
  31. package/starter/04-architecture.md +3 -0
  32. package/starter/05-whats-new.md +76 -12
  33. package/starter/examples.css +15 -0
  34. package/starter/01-guide/07-migrate-to-beta.md +0 -24
@@ -0,0 +1,176 @@
1
+ ---
2
+ title: When it breaks
3
+ description: The failures this tool actually produces, what causes each one, and the fix — by the symptom you see.
4
+ tags: [guide, troubleshooting]
5
+
6
+ jsonld:
7
+ type: TechArticle
8
+ breadcrumbs: true
9
+ ---
10
+
11
+ # When it breaks
12
+
13
+ Every expected error carries a message **and a hint** saying what is expected
14
+ instead, and the command exits with code 1 without a stack trace. The failures
15
+ below are the ones that say nothing, or say it somewhere you were not looking:
16
+ the site builds, the suite is green, and something is still wrong.
17
+
18
+ Each heading is the symptom as you would describe it.
19
+
20
+ ## `init` asked me nothing and built a site I did not choose
21
+
22
+ The dialogue needs a real terminal. Run from a script, a pipeline or through a
23
+ pipe, `init` takes the options given on the command line and the defaults for
24
+ everything else — it announces this before starting, unless `--yes` says you
25
+ meant it.
26
+
27
+ Pass what matters explicitly:
28
+
29
+ ```bash
30
+ npx docpensieve init my-site --name "Acme docs" --theme custom --site-url https://acme.example.com
31
+ ```
32
+
33
+ ## `npx` runs a version older than the one I just published
34
+
35
+ A freshly published version takes a few minutes to reach every mirror of the
36
+ registry, and `npx` keeps what it already has. During those minutes,
37
+ installing can even fail outright with `ETARGET`, because the packages pin each
38
+ other to the exact same version and one of the five is not there yet.
39
+
40
+ ```bash
41
+ npx --prefer-online docpensieve@latest init my-site
42
+ ```
43
+
44
+ Wait rather than republish: a version number burned is not recoverable, and
45
+ nothing is wrong with the one being propagated.
46
+
47
+ ## The site works on my machine and every link is dead online
48
+
49
+ The site is served from a subfolder and `baseUrl` was not set. Locally,
50
+ `serve` and `dev` answer at the root, so nothing shows.
51
+
52
+ ```js
53
+ siteUrl: 'https://acme.example.com/docs',
54
+ baseUrl: '/docs/',
55
+ ```
56
+
57
+ `baseUrl` prefixes every internal link. Without it, a link written
58
+ `/guide/installation/` points one level above the site.
59
+
60
+ ## `check` says a link "ignores the deployment prefix"
61
+
62
+ The file exists, but the link was written without the prefix — it will lead
63
+ nowhere once online, and hitting it locally hides that.
64
+
65
+ ```
66
+ index.html
67
+ /guide/installation/
68
+ → ignores the deployment prefix "/docs/"
69
+ ```
70
+
71
+ Write the link the way the documentation does — relative to the page
72
+ (`../components/card/`) or from the root of the version (`/components/card/`,
73
+ which the build rewrites) — rather than typing the deployed path by hand.
74
+
75
+ ## My page does not appear anywhere
76
+
77
+ Three causes, in the order worth checking:
78
+
79
+ - **`draft: true`** in its frontmatter keeps it out of the output, on purpose.
80
+ - **The file is outside a version folder.** Only what lives under a `folder`
81
+ declared in `versions` is read.
82
+ - **The extension.** `.md` and `.mdx` are read; nothing else is.
83
+
84
+ The build says nothing in these three cases, because none of them is an error:
85
+ a folder you did not declare is simply not documentation.
86
+
87
+ ## A colour I set in `tokens` changes nothing
88
+
89
+ `theme.tokens` sets the **light** palette. A site carrying
90
+ `theme.darkMode: 'dark'` is always dark, so it never shows those values — the
91
+ dark ones come from the theme's stylesheet.
92
+
93
+ Redefine them in the `theme/` folder, for both ways of being dark:
94
+
95
+ ```css
96
+ @media (prefers-color-scheme: dark) {
97
+ :root:not(.light) {
98
+ --dp-accent: #a78bfa;
99
+ }
100
+ }
101
+
102
+ :root.dark {
103
+ --dp-accent: #a78bfa;
104
+ }
105
+ ```
106
+
107
+ ## Node warns at every build, or refuses the configuration
108
+
109
+ A `.js` configuration file is read as a module only when the nearest
110
+ `package.json` says `"type": "module"` — and the one `npm init -y` writes says
111
+ the opposite, which makes Node refuse the file outright.
112
+
113
+ Name it `docpensieve.config.mjs`. The extension settles it whatever the
114
+ `package.json` says.
115
+
116
+ The build also stops when **both** files are present:
117
+
118
+ ```
119
+ Two configuration files in /path/to/project: docpensieve.config.mjs and docpensieve.config.js.
120
+ ```
121
+
122
+ Keep one. Guessing which you meant would sooner or later publish a site built
123
+ from the wrong settings.
124
+
125
+ ## A component stops the build, or renders nothing
126
+
127
+ The components that need a parent check for it: a `Column` outside `Columns`,
128
+ a `MenuLink` outside a `Menu` stop the build rather than render an element
129
+ with no effect. That is deliberate — a tag that quietly does nothing survives
130
+ review, an error does not.
131
+
132
+ `globalComponents: false` removes the shipped components from every page. A
133
+ page still using one then stops the build, naming it:
134
+
135
+ ```
136
+ Error Unknown component "Cards" in docs/v1.0/99-docpensieve/01-guide/index.mdx.
137
+ No global component is registered for this compilation.
138
+ ```
139
+
140
+ Either turn them back on, or remove the installed documentation with the
141
+ components it uses — `--minimal` at `init` leaves it out from the start.
142
+
143
+ A `.md` page using a component works, but its extension no longer says what the
144
+ page does. Rename it `.mdx`; the two compile identically.
145
+
146
+ ## A utility class in a page has no effect
147
+
148
+ Under the `tailwind` theme, the stylesheet is compiled from the classes found
149
+ in the **rendered** pages. A class whose name does not survive being written
150
+ into HTML is never emitted, and nothing reports it — an `&` becomes `&`,
151
+ so `[&_.x]:underline` produces no rule.
152
+
153
+ Pass through a variable of the component instead:
154
+
155
+ ```mdx
156
+ <Skill name="Coverage" level={97} color="#10b981" />
157
+ ```
158
+
159
+ ## Nothing above matches
160
+
161
+ Run the two commands that look at different things, in this order:
162
+
163
+ ```bash
164
+ npx docpensieve build
165
+ npx docpensieve check
166
+ ```
167
+
168
+ The build compiles pages and stops on what it cannot do. `check` reads the
169
+ produced site back and follows its links — a renamed page compiles perfectly
170
+ and leaves every link to it dead.
171
+
172
+ An unexpected failure — one without a hint — comes out with its stack trace,
173
+ and is worth reporting with the version printed by `npx docpensieve --version`.
174
+
175
+ Each message comes from a command, and the [CLI reference](../reference/cli/)
176
+ lists what each one does, with the exit codes a pipeline reads.
@@ -0,0 +1,160 @@
1
+ ---
2
+ title: Languages
3
+ description: Publish the same version in several languages — the folders, the addresses, and what happens to a page nobody translated.
4
+ tags: [guide, languages]
5
+
6
+ jsonld:
7
+ type: TechArticle
8
+ breadcrumbs: true
9
+ ---
10
+
11
+ # Languages
12
+
13
+ A version can be published in several languages. Each one is a folder of pages
14
+ of its own, standing beside the version it translates.
15
+
16
+ ## Declaring a translation
17
+
18
+ ```js
19
+ lang: 'en',
20
+ versions: [
21
+ {
22
+ slug: 'latest',
23
+ name: '1.0',
24
+ folder: 'docs/v1.0',
25
+ current: true,
26
+ translations: { fr: 'docs/v1.0-fr' },
27
+ },
28
+ ],
29
+ ```
30
+
31
+ `lang` is the language of the site — the one your pages are written in. Every
32
+ entry of `translations` names a language and the folder holding that
33
+ translation.
34
+
35
+ ```
36
+ docs/
37
+ ├── v1.0/ the language of the site
38
+ │ ├── index.md
39
+ │ └── guide/
40
+ └── v1.0-fr/ its French translation
41
+ ├── index.md
42
+ └── guide/
43
+ ```
44
+
45
+ ## The addresses
46
+
47
+ The language of the site keeps the addresses it has; a translation is served
48
+ under its code:
49
+
50
+ | Page | Address |
51
+ | ----------------- | ------------------------------------ |
52
+ | The site language | `/versions/latest/guide/install/` |
53
+ | Its French twin | `/versions/latest/fr/guide/install/` |
54
+
55
+ Nothing already published moves, which is the point: a link shared last year
56
+ still leads where it led. The translation lives **inside** the version, so a
57
+ version is still one folder, one orphan branch, one stylesheet.
58
+
59
+ ## A page nobody translated
60
+
61
+ It does not exist in that language. It is absent from the menu of that
62
+ language and from the sitemap, no `hreflang` claims it, and the language
63
+ switcher names the language without offering it — a reader learns the site has
64
+ a French version, and is never handed English under a French address.
65
+
66
+ That is what lets a translation start with five pages. Translate what matters
67
+ first; the rest stays in the language it was written in until someone gets to
68
+ it.
69
+
70
+ ## The words around your pages
71
+
72
+ The menu, the notices, the search field and the dates follow the language of
73
+ the page. English and French ship with the tool.
74
+
75
+ A language it does not ship keeps the English wording, and the `ui` field is
76
+ where a project writes its own — or corrects a word:
77
+
78
+ ```js
79
+ lang: 'de',
80
+ ui: {
81
+ de: { search: 'Suchen', onThisPage: 'Auf dieser Seite' },
82
+ },
83
+ ```
84
+
85
+ Anything left out stays in English rather than empty: half a translation is
86
+ still a readable page. A key that does not exist stops the build, listing those
87
+ that do — a typo there would leave the shipped word in place without a word.
88
+
89
+ ### Counting, in the plurals of the language
90
+
91
+ One key is not a word but a set of them: `pages`, which follows a number.
92
+
93
+ ```js
94
+ ui: {
95
+ pl: { pages: { one: 'strona', few: 'strony', many: 'stron', other: 'stron' } },
96
+ },
97
+ ```
98
+
99
+ The categories are those of **CLDR**, the reference the browsers and Node
100
+ carry: English has two — `one` and `other` — French puts zero in the singular,
101
+ Polish has four and Arabic six. The build picks the right one through
102
+ `Intl.PluralRules` rather than comparing the count with one, which is an
103
+ English rule.
104
+
105
+ Fill `other` at least: it is the category every language has, and it answers
106
+ for the ones a translation leaves out.
107
+
108
+ ## What the language decides by itself
109
+
110
+ Three things follow from the code alone, with nothing to declare:
111
+
112
+ | From the language | What it sets |
113
+ | -------------------- | ---------------------------------------------------- |
114
+ | Writing direction | `dir="rtl"` on `<html>` for Arabic, Hebrew, Persian… |
115
+ | Plural of a count | The CLDR categories above |
116
+ | The date of a byline | Written in the conventions of that language |
117
+
118
+ The codes themselves are **BCP 47** tags — `fr`, `pt-BR`, `zh-Hans` — the
119
+ standard `<html lang>` and `hreflang` expect. A tag that is not one stops the
120
+ build: it would otherwise put in the markup a value no browser or screen reader
121
+ knows how to read.
122
+
123
+ Search engines also receive `hreflang="x-default"`, pointing at the language
124
+ the pages are written in: it is the address to serve a reader whose own
125
+ language the site does not have.
126
+
127
+ ## What each language has of its own
128
+
129
+ | Its own | Shared with the version |
130
+ | ------------------------------------ | --------------------------------- |
131
+ | Pages, menu, search page | The stylesheet |
132
+ | Author file and images of its folder | The logo and favicon |
133
+ | The wording of the shell | The version notice and its number |
134
+
135
+ The author file is read in each folder, so a biography can be translated with
136
+ the pages it signs.
137
+
138
+ ## What is refused
139
+
140
+ Three mistakes stop the build rather than publish something wrong in silence:
141
+
142
+ | Written | Why it is refused |
143
+ | ----------------------------------------- | --------------------------------------------------------------- |
144
+ | `translations` at the root of the file | It belongs to a version — a version is what has pages |
145
+ | `lang` on a version | The site has one language; a version carries translations of it |
146
+ | A tag that names no language — `francais` | It would land in the markup as `lang="francais"` |
147
+
148
+ The third deserves a word. BCP 47 allows a language subtag of five to eight
149
+ letters, so `francais` is **well formed** — it is simply not a language. Left
150
+ to pass, it would serve English wording under a French address, and nothing
151
+ would say so.
152
+
153
+ ## What to check
154
+
155
+ - **A translation folder named but missing** stops the build, naming the
156
+ language rather than the folder alone.
157
+ - **`check` after a translation lands**: a page added on one side and linked
158
+ from the other is the usual first dead link.
159
+ - Use the same page paths on both sides. A page translated under a different
160
+ file name is a different page: it has no twin, and the switcher says so.
@@ -196,7 +196,7 @@ carrying the caption.
196
196
  <Column span={4}>
197
197
  <Card className="h-full text-center">
198
198
  <CardBody>
199
- <div className="text-4xl font-semibold text-indigo-600">9</div>
199
+ <div className="text-4xl font-semibold text-indigo-600">12</div>
200
200
  <div className="text-sm">components</div>
201
201
  </CardBody>
202
202
  <CardFooter>usable without an import</CardFooter>
@@ -229,7 +229,7 @@ carrying the caption.
229
229
  <Column span={4}>
230
230
  <Card className="full-height centered">
231
231
  <CardBody>
232
- <div className="figure">9</div>
232
+ <div className="figure">12</div>
233
233
  <div className="small">components</div>
234
234
  </CardBody>
235
235
  <CardFooter>usable without an import</CardFooter>
@@ -284,3 +284,6 @@ distributes as it likes; with `Columns`, each block stays where you put it.
284
284
 
285
285
  Below tablet width, the grid columns stack one under the other. Shrink the
286
286
  window to check.
287
+
288
+ The most frequent row is a row of cards; [Card](./card/) shows how to keep
289
+ them to the same height, which a grid alone does not do.
@@ -118,3 +118,7 @@ integration, whose time zone is not yours.
118
118
 
119
119
  A misspelt duration stops the build, and so does an invalid date. The message
120
120
  names the faulty value and recalls the expected format.
121
+
122
+ Announcing a release is what this is usually for, and [Versions](../guide/versions/)
123
+ covers the rest of that cycle — the banner a prerelease carries on its own,
124
+ and the day the two tracks swap.
@@ -82,6 +82,43 @@ The bubble is capped at about sixteen rem wide, then wraps.
82
82
 
83
83
  See the <Tooltip text="A published version stays in the state in which it was produced. Rebuilding it is never necessary, and nothing would guarantee it gives the same result years later.">reason for version branches</Tooltip> for the details.
84
84
 
85
+ ## The look through className
86
+
87
+ A `className` lands on the term, not on the bubble: it is the term the reader
88
+ sees before hovering anything.
89
+
90
+ <ForTheme framework="tailwind">
91
+
92
+ A term marked <Tooltip className="text-indigo-500 font-medium" text="It now carries the accent colour, and a heavier weight.">more firmly</Tooltip> than the others.
93
+
94
+ ```mdx
95
+ <Tooltip className="text-indigo-500 font-medium" text="…">
96
+ more firmly
97
+ </Tooltip>
98
+ ```
99
+
100
+ </ForTheme>
101
+ <ForTheme framework="custom">
102
+
103
+ A term marked <Tooltip className="accent-text" text="It now carries the accent colour of the theme.">more firmly</Tooltip> than the others.
104
+
105
+ ```mdx
106
+ <Tooltip className="accent-text" text="…">
107
+ more firmly
108
+ </Tooltip>
109
+ ```
110
+
111
+ ```css
112
+ /* theme/99-docpensieve.css */
113
+ .accent-text {
114
+ color: var(--dp-accent);
115
+ }
116
+ ```
117
+
118
+ </ForTheme>
119
+
120
+ `style` works the same way, for a one-off adjustment that needs no class.
121
+
85
122
  ## What it cannot do
86
123
 
87
124
  The bubble is positioned absolutely: inside a container that **clips its
@@ -98,3 +135,6 @@ leaves the term.
98
135
 
99
136
  A `Tooltip` without text stops the build: a hover with no effect goes
100
137
  unnoticed for a long time. So does an unknown side.
138
+
139
+ A tooltip explains a term without leaving the sentence. A remark that needs
140
+ its own space is an [Admonition](./admonition/) instead.
@@ -191,3 +191,7 @@ announcing it would lie about what the page can do.
191
191
  A `TreeItem` written outside a `Tree` is refused — it would produce an `<li>`
192
192
  outside any list, invalid HTML that no browser reports. An entry without a
193
193
  label is refused too.
194
+
195
+ A tree is how this documentation shows the shape of a project. What those
196
+ folders become once built — the order, the addresses — is in
197
+ [Writing pages](../guide/writing-pages/).
@@ -141,3 +141,6 @@ And a less happy side effect, better known in advance: what depends on the
141
141
  moment is frozen at build time. A page that shows “the offer ends tomorrow”
142
142
  will still say so in six months if the site has not been rebuilt. A scheduled
143
143
  build is enough to keep it right.
144
+
145
+ Setting one up takes a few lines in the pipeline that already builds the
146
+ site: [Deployment](../guide/deployment/) has the recipe.
@@ -157,6 +157,36 @@ every gauge it holds.
157
157
  keeps announcing it to screen readers. Hiding a piece of information is not
158
158
  deleting it.
159
159
 
160
+ ## A name that is not a text
161
+
162
+ `name` accepts more than a string — an icon beside a word, a term carrying a
163
+ tooltip. A screen reader then has nothing to announce, so `label` says in plain
164
+ words what the gauge measures:
165
+
166
+ <Skill
167
+ name={
168
+ <>
169
+ <LogoIcon src="./icons/shield.svg" /> Coverage
170
+ </>
171
+ }
172
+ label="Test coverage"
173
+ level={97}
174
+ />
175
+
176
+ ```mdx
177
+ <Skill
178
+ name={
179
+ <>
180
+ <LogoIcon src="./icons/shield.svg" /> Coverage
181
+ </>
182
+ }
183
+ label="Test coverage"
184
+ level={97}
185
+ />
186
+ ```
187
+
188
+ With a plain string in `name`, `label` is not needed: the name already says it.
189
+
160
190
  ## A dashboard
161
191
 
162
192
  Several gauges in columns, each in its card.
@@ -139,6 +139,27 @@ readers — which is right when nearby text already says the same thing. With
139
139
  In the list above, the icons have no `label`: the text next to them already
140
140
  carries the meaning, and announcing it twice would tell nothing.
141
141
 
142
+ ## An icon from a set
143
+
144
+ Beside a file of your project, `src` accepts the name of an icon from a
145
+ collection, written `prefix:name`:
146
+
147
+ ```mdx
148
+ <LogoIcon src="simple-icons:github" label="Repository" />
149
+ ```
150
+
151
+ The set is a package your project installs, and the drawing is placed in the
152
+ page at the build, exactly as a file of yours would be:
153
+
154
+ ```bash
155
+ npm install --save-dev @iconify-json/simple-icons
156
+ ```
157
+
158
+ Nothing is fetched while the page is read — those collections offer an online
159
+ service, which would mean a request for every icon and a script to make it.
160
+ A set that is not installed stops the build and names the package to install;
161
+ so does a name the set does not hold.
162
+
142
163
  ## What it refuses
143
164
 
144
165
  A missing file, a file without an `svg` tag, a target leaving the version
@@ -147,3 +168,6 @@ would otherwise leave an empty box that nobody notices.
147
168
 
148
169
  The scripts and event handlers present in a file are removed before inlining.
149
170
  A component loads no JavaScript, and this one is not going to introduce any.
171
+
172
+ A kind of admonition takes its mark through this same component — see
173
+ [Admonition](./admonition/).
@@ -43,3 +43,6 @@ This site is built with the **custom** theme.
43
43
  A `framework` other than `tailwind` or `custom` stops the build, with the
44
44
  accepted values: a typo would otherwise hide the content under every theme,
45
45
  without a word.
46
+
47
+ Which theme a project runs is one field of its configuration, and the whole
48
+ of what it changes is in [Themes](../guide/themes/).
@@ -0,0 +1,129 @@
1
+ ---
2
+ title: Menu
3
+ description: A menu of links placed anywhere in a page, folded behind a button on a narrow screen.
4
+ ---
5
+
6
+ # Menu
7
+
8
+ The header carries the navigation of the site, and the sidebar that of the
9
+ documentation. This menu belongs to a **page**: the links a section offers
10
+ where it stands — a summary at the top of a landing page, the chapters of a
11
+ guide, the entries of a portal.
12
+
13
+ ## A row of links
14
+
15
+ <Menu label="Guide">
16
+ <MenuLink href="../guide/installation/">Installation</MenuLink>
17
+ <MenuLink href="../guide/first-site/">First site</MenuLink>
18
+ <MenuLink href="../guide/writing-pages/">Writing pages</MenuLink>
19
+ </Menu>
20
+
21
+ ```mdx
22
+ <Menu label="Guide">
23
+ <MenuLink href="../guide/installation/">Installation</MenuLink>
24
+ <MenuLink href="../guide/first-site/">First site</MenuLink>
25
+ </Menu>
26
+ ```
27
+
28
+ A target resolves as anywhere else: relative to the page, or absolute from the
29
+ version root. `label` names the menu for screen readers, and labels the button
30
+ it folds into.
31
+
32
+ ## Groups
33
+
34
+ An entry may open a group rather than lead somewhere:
35
+
36
+ <Menu label="Sections">
37
+ <MenuLink href="../guide/">Guide</MenuLink>
38
+ <MenuGroup title="Reference">
39
+ <MenuLink href="../reference/cli/">Commands</MenuLink>
40
+ <MenuLink href="../reference/configuration/">Configuration</MenuLink>
41
+ <MenuLink href="../reference/frontmatter/">Frontmatter</MenuLink>
42
+ </MenuGroup>
43
+ <MenuLink href="../components/">Components</MenuLink>
44
+ </Menu>
45
+
46
+ ```mdx
47
+ <MenuGroup title="Reference">
48
+ <MenuLink href="../reference/cli/">Commands</MenuLink>
49
+ </MenuGroup>
50
+ ```
51
+
52
+ In the row the group opens as a panel below its title. Once the menu is folded,
53
+ it unfolds in place instead: on a narrow screen a panel would open off screen.
54
+
55
+ ## On a narrow screen
56
+
57
+ Below 40rem the row gives way to a button carrying the `label`. Everything is
58
+ written once and rendered twice — a row and a fold — of which the stylesheet
59
+ shows one.
60
+
61
+ That costs a little markup, and buys a menu that works with JavaScript turned
62
+ off: the button is a native element. The lighter alternative relies on a recent
63
+ CSS pseudo-element, and where the browser does not know it, the links vanish
64
+ altogether.
65
+
66
+ **Narrow this window** to see the row become a button.
67
+
68
+ ## Styling
69
+
70
+ The menu takes the palette of the active theme through the `--dp-*` tokens.
71
+ What it does not decide is how far it stands from the text, or whether it sits
72
+ in a box — that is set at use.
73
+
74
+ <ForTheme framework="tailwind">
75
+
76
+ A `className` holds utilities:
77
+
78
+ <Menu className="rounded-lg border border-slate-200 p-3 dark:border-slate-800" label="Boxed">
79
+ <MenuLink href="../guide/">Guide</MenuLink>
80
+ <MenuLink href="../reference/">Reference</MenuLink>
81
+ </Menu>
82
+
83
+ ```mdx
84
+ <Menu className="rounded-lg border border-slate-200 p-3 dark:border-slate-800">
85
+ <MenuLink href="../guide/">Guide</MenuLink>
86
+ </Menu>
87
+ ```
88
+
89
+ </ForTheme>
90
+ <ForTheme framework="custom">
91
+
92
+ A `className` names a class of your own, written in the `theme/` folder:
93
+
94
+ <Menu className="menu-boxed" label="Boxed">
95
+ <MenuLink href="../guide/">Guide</MenuLink>
96
+ <MenuLink href="../reference/">Reference</MenuLink>
97
+ </Menu>
98
+
99
+ ```mdx
100
+ <Menu className="menu-boxed">
101
+ <MenuLink href="../guide/">Guide</MenuLink>
102
+ </Menu>
103
+ ```
104
+
105
+ ```css
106
+ /* theme/99-docpensieve.css */
107
+ .menu-boxed {
108
+ padding: 0.75rem 1rem;
109
+ border: 1px solid var(--dp-border);
110
+ border-radius: var(--dp-radius);
111
+ background: var(--dp-bg-soft);
112
+ }
113
+ ```
114
+
115
+ </ForTheme>
116
+
117
+ A one-off adjustment needs no class at all: `style` understands the tokens
118
+ under both themes.
119
+
120
+ ```mdx
121
+ <Menu style={{ marginBlock: '2.5rem' }}>…</Menu>
122
+ ```
123
+
124
+ ## What it refuses
125
+
126
+ A `MenuLink` or a `MenuGroup` written outside a `Menu` stops the build, and so
127
+ does an entry without a target or a group without a title. Each of these would
128
+ otherwise render something that looks like a menu and leads nowhere — the kind
129
+ of fault a review does not catch.