diff --git a/.changeset/eighty-pugs-repeat.md b/.changeset/eighty-pugs-repeat.md index b5c3d0fc05..930021bdc1 100644 --- a/.changeset/eighty-pugs-repeat.md +++ b/.changeset/eighty-pugs-repeat.md @@ -12,7 +12,7 @@ The resulting scroll-into-view puts the list behind React Aria's 300ms `pointer- cooldown, which swallows a click on an option. The observable failure was confined to browser-mode test runs, where the width never becomes -finite. In a real browser the first-pass window never reaches the DOM — `ScrollView` settles its -width before commit — so no change in behaviour is expected for `Select`, `ComboBox`, +finite. In a real browser the first-pass window never reaches the DOM, because `ScrollView` settles its +width before commit, so no change in behaviour is expected for `Select`, `ComboBox`, `Autocomplete` or `TagField`. `ListBoxLayout` now rejects measurements taken in that state, closing both paths. diff --git a/.changeset/olive-mice-brake.md b/.changeset/olive-mice-brake.md index a82270a0fd..0353240cc5 100644 --- a/.changeset/olive-mice-brake.md +++ b/.changeset/olive-mice-brake.md @@ -6,4 +6,4 @@ classes. It renders the positioned box itself and lets the child fill it, so a Fragment or a non-element child is placed like anything else, and a `className` you set on the icon or action is left alone instead of being merged. Rendered -geometry is unchanged; the icon and action each gain a wrapping ``. +geometry is unchanged. The icon and action each gain a wrapping ``. diff --git a/.changeset/prose-style-as-lint.md b/.changeset/prose-style-as-lint.md new file mode 100644 index 0000000000..15d01fdc76 --- /dev/null +++ b/.changeset/prose-style-as-lint.md @@ -0,0 +1,16 @@ +--- +'@marigold/docs': patch +--- + +Prose style is now enforced rather than remembered. Vale runs over the docs site, the +changesets and the published READMEs, wired into the pre-commit hook and a CI check, with +the rules in `.vale/styles/Marigold/` and the writing guidance in `CLAUDE.md`. + +Three rules block CI: no em dashes, no semicolons in prose, and no en dash asides. Table +cells are exempt, because there an em dash is a legitimate "not applicable" marker, and +ranges keep the en dash so quoted component output stays accurate. All 135 existing +violations across 30 files are rewritten in this change, so the check starts green. + +The en dash rule matches only the aside form (a letter, a spaced en dash, then a lowercase +letter). German uses a spaced en dash where English uses an em dash, which makes it an easy +slip, but a range reads as digits or a capital around the dash and stays legal. diff --git a/.changeset/stepper.md b/.changeset/stepper.md index 214e7ad32b..c5cb285868 100644 --- a/.changeset/stepper.md +++ b/.changeset/stepper.md @@ -11,4 +11,4 @@ feat(DST-1391): add `Stepper`, a progress indicator for multi-step tasks. State is entirely consumer-owned. `completedKeys` is a set rather than a high-water mark, so non-contiguous completion coming from a server is expressible, and the component never infers that a step is finished: only your code knows whether validation passed. `selectableKeys` replaces the built-in "completed, errored, or current" rule when a backend decides what is reachable, and `disabledKeys` always wins over both. Errored steps stay clickable by default, so a user who is told a step failed has a way back to it, unless `selectableKeys` leaves them out. -Steps with an `href` render as real links and route through `RouterProvider`; steps without one render as buttons. Steps that are not reachable render as plain text rather than as disabled controls, since an unreachable step is not a disabled widget. `hideLabels` drops labels visually for flows with too many steps to label, keeping them for screen readers and adding a visible "Step 3 of 5" counter so sighted users still know how far along they are. +Steps with an `href` render as real links and route through `RouterProvider`. Steps without one render as buttons. Steps that are not reachable render as plain text rather than as disabled controls, since an unreachable step is not a disabled widget. `hideLabels` drops labels visually for flows with too many steps to label, keeping them for screen readers and adding a visible "Step 3 of 5" counter so sighted users still know how far along they are. diff --git a/.github/renovate.json b/.github/renovate.json index 8b7644e96f..12f9cef995 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -5,10 +5,8 @@ ":rebaseStalePrs" ], "timezone": "Europe/Berlin", - "ignoreScripts": true, "branchConcurrentLimit": 5, - "packageRules": [ { "matchDatasources": ["npm"], @@ -117,7 +115,7 @@ "automerge": true }, { - "description": "Hold @react-types/* at their last type-only versions (button ~3.15, checkbox ~3.10, grid ~3.3, table ~3.13). Newer minors invert the dependency direction: the type-only packages start depending on the full @react-spectrum/* component packages, which drag in @adobe/react-spectrum and a duplicate react-aria/react-stately copy, splitting the i18n + overlay React contexts. See #5485 and upstream https://github.com/adobe/react-spectrum/issues/10139. Patches within the type-only line stay allowed; minor/major are blocked (and must not automerge). Note that some of these (e.g. @react-types/grid 3.4.0) pull @react-spectrum/provider via a *peerDependency*, so `npm view dependencies` looks clean while pnpm still auto-installs the peer — verify with `scripts/check-react-aria-dedupe.mjs` after any install, not by reading the manifest. Drop this rule once Adobe ships type-only @react-types/* again.", + "description": "Hold @react-types/* at their last type-only versions (button ~3.15, checkbox ~3.10, grid ~3.3, table ~3.13). Newer minors invert the dependency direction: the type-only packages start depending on the full @react-spectrum/* component packages, which drag in @adobe/react-spectrum and a duplicate react-aria/react-stately copy, splitting the i18n + overlay React contexts. See #5485 and upstream https://github.com/adobe/react-spectrum/issues/10139. Patches within the type-only line stay allowed; minor/major are blocked (and must not automerge). Note that some of these (e.g. @react-types/grid 3.4.0) pull @react-spectrum/provider via a *peerDependency*, so `npm view dependencies` looks clean while pnpm still auto-installs the peer \u2014 verify with `scripts/check-react-aria-dedupe.mjs` after any install, not by reading the manifest. Drop this rule once Adobe ships type-only @react-types/* again.", "matchPackageNames": [ "@react-types/button", "@react-types/checkbox", @@ -126,6 +124,12 @@ ], "matchUpdateTypes": ["minor", "major"], "enabled": false + }, + { + "description": "Vale minors change rule semantics (native MDX in 3.18, JSX-children scoping in 3.19), so review the bump instead of automerging it", + "matchDepNames": ["vale-cli/vale"], + "automerge": false, + "minimumReleaseAge": "3 days" } ], "vulnerabilityAlerts": { @@ -134,5 +138,16 @@ "schedule": ["at any time"], "minimumReleaseAge": "0 days" }, - "osvVulnerabilityAlerts": true + "osvVulnerabilityAlerts": true, + "customManagers": [ + { + "customType": "regex", + "description": "Vale is a downloaded binary, not an npm dependency, so no native manager sees its pin", + "managerFilePatterns": ["/^scripts/install-vale\\.mjs$/"], + "matchStrings": ["const VERSION = '(?[^']+)';"], + "depNameTemplate": "vale-cli/vale", + "datasourceTemplate": "github-releases", + "extractVersionTemplate": "^v(?.+)$" + } + ] } diff --git a/.github/workflows/prose.yml b/.github/workflows/prose.yml new file mode 100644 index 0000000000..bd57adf466 --- /dev/null +++ b/.github/workflows/prose.yml @@ -0,0 +1,24 @@ +name: Prose + +on: ['push'] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + prose: + name: Check prose style + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: Setup Node (using .node-version) + uses: actions/setup-node@v6 + with: + node-version-file: '.node-version' + + - name: Install Vale (pinned in the script, checksum verified) + run: node scripts/install-vale.mjs + + - name: Check prose style + run: .vale/bin/vale . diff --git a/.gitignore b/.gitignore index 6ea87a2f1e..ef98e4e06f 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,5 @@ next-env.d.ts # Vitest browser-mode failure artifacts **/__screenshots__ .vitest-attachments + +.vale/bin/ diff --git a/.husky/pre-commit b/.husky/pre-commit index 303d13c3bc..efb3c7017f 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/bin/sh export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" -npx lint-staged +npx lint-staged --relative diff --git a/.lintstagedrc b/.lintstagedrc index 7db0bc7512..d6337b8d5b 100644 --- a/.lintstagedrc +++ b/.lintstagedrc @@ -1,3 +1,3 @@ { - "*": "prettier --write" + "*": ["prettier --write", "node scripts/install-vale.mjs", ".vale/bin/vale"] } diff --git a/.prettierignore b/.prettierignore index 9d35150660..ecdba4d57c 100644 --- a/.prettierignore +++ b/.prettierignore @@ -13,6 +13,7 @@ .npmrc .nvmrc .prettierignore +.vale.ini .claude coverage diff --git a/.vale.ini b/.vale.ini new file mode 100644 index 0000000000..9ad6c0fdd4 --- /dev/null +++ b/.vale.ini @@ -0,0 +1,21 @@ +StylesPath = .vale/styles + +MinAlertLevel = suggestion + +[docs/content/**/*.mdx] +BasedOnStyles = Marigold + +[.changeset/*.md] +BasedOnStyles = Marigold + +[{README.md,contributing.md,CODE-OF-CONDUCT.md,MIGRATION-v18.md}] +BasedOnStyles = Marigold + +[{packages,themes,config}/*/README.md] +BasedOnStyles = Marigold + +[docs/content/releases/**/release.mdx] +BasedOnStyles = + +[packages/*/src/**] +BasedOnStyles = diff --git a/.vale/styles/Marigold/EmDash.yml b/.vale/styles/Marigold/EmDash.yml new file mode 100644 index 0000000000..2197360a52 --- /dev/null +++ b/.vale/styles/Marigold/EmDash.yml @@ -0,0 +1,10 @@ +extends: existence +message: 'Rephrase with a comma, a colon, or a second sentence instead of an em dash.' +level: error +nonword: true +scope: + - paragraph + - list + - heading +tokens: + - '—' diff --git a/.vale/styles/Marigold/EnDash.yml b/.vale/styles/Marigold/EnDash.yml new file mode 100644 index 0000000000..2470473ff3 --- /dev/null +++ b/.vale/styles/Marigold/EnDash.yml @@ -0,0 +1,10 @@ +extends: existence +message: 'Use a comma, a colon, or a second sentence instead of an en dash aside. German uses a spaced en dash where English uses an em dash. Ranges like `4–9` or `Jan 1 – Dec 31` are fine.' +level: error +nonword: true +scope: + - paragraph + - list + - heading +tokens: + - '[a-zA-Z] – [a-z]' diff --git a/.vale/styles/Marigold/Semicolon.yml b/.vale/styles/Marigold/Semicolon.yml new file mode 100644 index 0000000000..8bab95cc80 --- /dev/null +++ b/.vale/styles/Marigold/Semicolon.yml @@ -0,0 +1,10 @@ +extends: existence +message: 'Use a period or a comma instead of a semicolon in prose.' +level: error +nonword: true +scope: + - paragraph + - list + - heading +tokens: + - ';' diff --git a/CLAUDE.md b/CLAUDE.md index 631ea38fd6..811abda192 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -71,11 +71,40 @@ The CLI fetches from the Marigold docs site, caches for 24h, and works offline ( - **Typecheck**: Run `pnpm typecheck:only` after code changes - **Lint**: Run `pnpm lint` to check code style +- **Prose**: Run `pnpm lint:prose` after editing docs prose (see [Prose Style](#prose-style)) - **Format**: Run `pnpm format` before committing - **Branch from**: `main` (use GitHub Flow) - **Changesets**: Use `pnpm changeset` for version management - **Storybook**: Run `pnpm sb` to preview components locally +## Prose Style + +Documentation prose is linted. `pnpm lint:prose` runs Vale over the docs site, the changesets +and the published READMEs, and the Prose CI check runs the same rules. The binary is pinned and downloaded by +`scripts/install-vale.mjs` on first use, and pinned there so Renovate can see it. + +- **No em dashes.** Rephrase with a comma, a colon, or a second sentence. In a + `- **Term** — definition` list item, write `- **Term**: definition`, which is already the + dominant form in these docs. +- **No semicolons in prose.** Use a period or a comma. Semicolons in code are untouched: + fenced blocks, code spans and MDX `import` statements are all outside the linted scope. +- **No en dash asides.** German uses a spaced en dash where English uses an em dash, so this + is an easy slip to make. Ranges keep the en dash: `4–9`, `Jan 1 – Dec 31`. + +Table cells are exempt from all three. There an em dash is a legitimate "not applicable" +marker, as in `| — (no class) |`. + +These rules govern prose written **for a reader**: `docs/content/**`, `.changeset/*.md`, +published package READMEs, and the repo's top-level markdown. They do **not** govern prose +written for an agent. `CLAUDE.md`, `.memory/**`, `.claude/**`, `docs/superpowers/**` and +`packages/*/src/**/README.md` are deliberately out of scope, and `.claude/README.md` positively +_requires_ an em dash in skill descriptions. Do not "fix" those files. Generated output +(`CHANGELOG.md`, `docs/content/releases/*/release.mdx`) is out of scope too, which is why +changesets are linted at the source instead. + +The rules live in `.vale/styles/Marigold/`. A rule at `error` blocks CI. A rule at `warning` is +advisory, which is how a new rule lands until its existing violations are cleaned up. + ## Monorepo Structure - `packages/components` - Core React components diff --git a/MIGRATION-v18.md b/MIGRATION-v18.md index b2769ad0b0..b669f14ccc 100644 --- a/MIGRATION-v18.md +++ b/MIGRATION-v18.md @@ -597,7 +597,7 @@ must remove the `SectionMessage.close` slot (see B6). - **`SelectList` `layout` / `keyboardNavigationBehavior`** removed. The list is always a `layout="grid"` GridList and react-aria derives `keyboardNavigationBehavior: 'tab'` from that, so neither value ever reached the - DOM. Drop them; behavior is unchanged. + DOM. Drop them. Behavior is unchanged. - **`Loader` `loaderType`**: the v17 JSDoc documented a `cycle` value that never existed. The type only ever accepted `xloader` and `circle` (default `circle`), so this is a doc fix, not a code change. Unrelated to the `Spinner` → `Loader` @@ -735,7 +735,7 @@ keeps every call site valid: | `Deal` | `BadgePercent` | `Zoom` | `ZoomIn` | | `Print` | `Printer` | | | -`Close` is not in the official mapping table; it maps to Lucide's `X`. Three legacy +`Close` is not in the official mapping table. It maps to Lucide's `X`. Three legacy names have no Lucide equivalent and each needs a decision: `PauseAlt` → `CirclePause`, `PlayAlt` → `CirclePlay`, and `Whatsapp` is gone with no replacement (Lucide dropped brand icons and it is not among the 13 retained ones, so vendor the diff --git a/README.md b/README.md index d1fe95de16..c9ef9276e3 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ To start documentation follow these steps: Releases are published by CI, not locally. `.github/workflows/release.yml` runs [`changesets/action`](https://github.com/changesets/action) on every push to `main`: it either opens a -"release: version packages" PR that consumes the pending changesets, or — once that PR is merged — +"release: version packages" PR that consumes the pending changesets, or, once that PR is merged, publishes the bumped packages to npm. So the only thing you do by hand is add a changeset with `pnpm changeset` when your change should @@ -97,7 +97,7 @@ cause a version bump. Prereleases use [changesets pre mode](https://github.com/changesets/changesets/blob/main/docs/prereleases.md), which lives on `main` (there is no separate release branch). The mode is recorded in -`.changeset/pre.json`, and `pnpm changeset publish` derives the npm dist-tag from its `tag` field — +`.changeset/pre.json`, and `pnpm changeset publish` derives the npm dist-tag from its `tag` field, so while pre mode is active, everything publishes to that tag instead of `latest`. ```sh diff --git a/docs/content/components/__internal__/component-guidelines.mdx b/docs/content/components/__internal__/component-guidelines.mdx index 5352ddc777..3b525fb34f 100644 --- a/docs/content/components/__internal__/component-guidelines.mdx +++ b/docs/content/components/__internal__/component-guidelines.mdx @@ -116,24 +116,24 @@ adds no signal. already captured by another, kept story (e.g. `Dialog.Basic` vs. the open-state Dialog stories). - The frame is **inherently flaky or non-deterministic** and can't be stabilized - (animations, time, random data) — and the same surface is covered by a stable story. + (animations, time, random data), and the same surface is covered by a stable story. -**Keep the snapshot when** the story is the only one capturing a particular visual — +**Keep the snapshot when** the story is the only one capturing a particular visual: a unique variant, size, open overlay, error/edge state, or layout. When in doubt, keep it. -**This only turns off the snapshot.** Always keep the `play` test and any docs example — +**This only turns off the snapshot.** Always keep the `play` test and any docs example. `disableSnapshot` does not affect them. **Mechanics:** -- **Named / base stories** — add the parameter directly. Generated `.test()` children +- **Named / base stories**: add the parameter directly. Generated `.test()` children inherit it via `combineParameters`, so disabling a base also disables its tests. -- **A single generated `.test()` story** — use the 3-arg overload +- **A single generated `.test()` story**: use the 3-arg overload `.test(name, annotations, fn)` so only the derivative is disabled, not its base. -- **Re-enabling a kept child** — if a base is disabled but one of its `.test()` children +- **Re-enabling a kept child**: if a base is disabled but one of its `.test()` children is the only frame capturing a state worth keeping, re-enable it explicitly with `chromatic: { disableSnapshot: false }`. -- **Don't leave dead config** — when you disable a snapshot, drop any now-unused +- **Don't leave dead config**: when you disable a snapshot, drop any now-unused `chromatic.delay` (or similar) that only existed to stabilize the removed capture. Example of how we write stories: diff --git a/docs/content/components/actions/toggle-button/index.mdx b/docs/content/components/actions/toggle-button/index.mdx index 5be332985d..1e5c1cd715 100644 --- a/docs/content/components/actions/toggle-button/index.mdx +++ b/docs/content/components/actions/toggle-button/index.mdx @@ -13,7 +13,7 @@ Use `` in toolbars, editors, and interactive surfaces, where the u `` is not a form field or a filter. It has no label, description, error message, or validation state. Reach for [Checkbox](/components/form/checkbox), [Radio](/components/form/radio), or [Switch](/components/form/switch) when you need a form field, and use a `` for filtering. -`` is a toolbar of independent on/off actions, not a selection control. Each button in the group toggles on its own, like formatting options, view options, or visible layers. When exactly one option must be selected at a time — switching a view, mode, or filter — use a [SegmentedControl](/components/form/segmented-control) instead. +`` is a toolbar of independent on/off actions, not a selection control. Each button in the group toggles on its own, like formatting options, view options, or visible layers. When exactly one option must be selected at a time, switching a view, mode, or filter, use a [SegmentedControl](/components/form/segmented-control) instead. ## Anatomy diff --git a/docs/content/components/collection/actionbar/index.mdx b/docs/content/components/collection/actionbar/index.mdx index d3790448af..37dc1e42f6 100644 --- a/docs/content/components/collection/actionbar/index.mdx +++ b/docs/content/components/collection/actionbar/index.mdx @@ -52,7 +52,7 @@ The most common home for an `` is a [``](/components/collectio -Inside the render prop you write only the actions; the selection count and the clear button are filled in for you. The bar stays pinned to the bottom of the scroll area as the user works down a long list. +Inside the render prop you write only the actions. The selection count and the clear button are filled in for you. The bar stays pinned to the bottom of the scroll area as the user works down a long list. When the user toggles the Table's select-all checkbox, the selection becomes @@ -101,7 +101,7 @@ Give every icon-only action a text label as well, either visible next to the ico ### Actions -Actions are plain [`
` pulls its first and last cell in while row backgrounds still reach the edge, and `` insets its header and content while its dividers span the full width. Custom content can read the same variable — see [Reading container spacing](/foundations/spacing#reading-container-spacing). +A bled `` publishes the card's horizontal padding as `--bleed-px`, so edge-aware children realign with the title on their own: `
` pulls its first and last cell in while row backgrounds still reach the edge, and `` insets its header and content while its dividers span the full width. Custom content can read the same variable. See [Reading container spacing](/foundations/spacing#reading-container-spacing). ## Accessibility diff --git a/docs/content/components/content/section-message/index.mdx b/docs/content/components/content/section-message/index.mdx index a262ec8594..5fca1a79fa 100644 --- a/docs/content/components/content/section-message/index.mdx +++ b/docs/content/components/content/section-message/index.mdx @@ -15,7 +15,7 @@ A `` is a container with a severity icon, an optional title, the - **Container**: Wraps the message on a neutral surface with a muted variant-colored border. - **Icon**: Variant icon that reinforces the severity. -- **Title** _(optional)_: Short headline summarizing the message. Renders as a semantic heading; adjust its level with the `headingLevel` prop so it fits the surrounding document outline. Don't end the title with a period, and don't repeat it in the content. +- **Title** _(optional)_: Short headline summarizing the message. Renders as a semantic heading. Adjust its level with the `headingLevel` prop so it fits the surrounding document outline. Don't end the title with a period, and don't repeat it in the content. - **Description** _(optional)_: A short summary directly below the title. - **Content**: Descriptive body text. This is the required core of the message. - **Close button** _(optional)_: Dismiss control when `closeButton` is set. diff --git a/docs/content/components/formatters/numericformat/index.mdx b/docs/content/components/formatters/numericformat/index.mdx index 0f2883d692..e7452ac01e 100644 --- a/docs/content/components/formatters/numericformat/index.mdx +++ b/docs/content/components/formatters/numericformat/index.mdx @@ -27,7 +27,7 @@ The `` component can also format a pair of values as a localized -For accessibility, screen readers will treat the formatted range as one continuous string (for example, "1,000 – 5,000"). This works well in most cases, but if your interface needs to convey the start and end values as distinct pieces of information, you can render them as two separate `` components and supply explicit ARIA labels. This ensures that assistive technologies announce each value in context. +For accessibility, screen readers will treat the formatted range as one continuous string (for example, "1,000–5,000"). This works well in most cases, but if your interface needs to convey the start and end values as distinct pieces of information, you can render them as two separate `` components and supply explicit ARIA labels. This ensures that assistive technologies announce each value in context. ```tsx diff --git a/docs/content/components/layout/page/index.mdx b/docs/content/components/layout/page/index.mdx index 9d100badcd..72625b9d97 100644 --- a/docs/content/components/layout/page/index.mdx +++ b/docs/content/components/layout/page/index.mdx @@ -99,7 +99,7 @@ Reach for an override only when a screen has a reason the defaults cannot antici - **Tighter padding** with `p` (or `px` and `py` for one axis) when the page is embedded in a surface that already supplies its own outer spacing. Set one or the other. Passing `p` together with `px` or `py` is a type error, since they would fight over the same edges. - **A different rhythm** with `space` when a sparse screen wants more air between the header and a lone panel, or when a dense screen reads better pulled tight. -Whatever those props resolve to is published as `--page-px`, `--page-py` and `--page-gap` on the Page root, so content in the subtree can line up with the page frame without repeating the value. They are read-only — see [Reading container spacing](/foundations/spacing#reading-container-spacing). +Whatever those props resolve to is published as `--page-px`, `--page-py` and `--page-gap` on the Page root, so content in the subtree can line up with the page frame without repeating the value. They are read-only. See [Reading container spacing](/foundations/spacing#reading-container-spacing). ### Content diff --git a/docs/content/components/layout/panel/index.mdx b/docs/content/components/layout/panel/index.mdx index 91ac1ea615..573d57b845 100644 --- a/docs/content/components/layout/panel/index.mdx +++ b/docs/content/components/layout/panel/index.mdx @@ -154,7 +154,7 @@ Tables, media, charts, and status bars are the exceptions. They look cramped whe -Tables get a small bonus: when bled, their cell edges line up with the Panel's header and footer, so what's inside a row still reads in rhythm with the title above it. That works because a bled content area publishes the Panel's horizontal padding as `--bleed-px`, which your own content can read too — see [Reading container spacing](/foundations/spacing#reading-container-spacing). +Tables get a small bonus: when bled, their cell edges line up with the Panel's header and footer, so what's inside a row still reads in rhythm with the title above it. That works because a bled content area publishes the Panel's horizontal padding as `--bleed-px`, which your own content can read too. See [Reading container spacing](/foundations/spacing#reading-container-spacing). An `` behaves the same way. Drop one into a bled content area and its item dividers span edge-to-edge while the header and content stay inset, aligned with the Panel title. No extra prop is needed: the Accordion picks up the Panel's horizontal padding automatically, exactly like Table cells do. @@ -262,7 +262,7 @@ Panel publishes what those props resolved to as three read-only custom propertie You need them when a wrapper interrupts the Panel's own rhythm. Panel spaces its slots with a flex gap, so a `
` around several `` siblings becomes a single flex item and the gap between them collapses. Re-declare it with `gap-(--panel-gap)` and the group stays in rhythm with every other Panel, where a hardcoded value would drift as soon as the `space` default changes. -Set spacing through the props, not by declaring these yourself — Panel writes them inline on its root, which outranks a declaration on any ancestor. For content that should align with the Panel's padding after bleeding out of it, reach for `--bleed-px` instead. See [Reading container spacing](/foundations/spacing#reading-container-spacing). +Set spacing through the props, not by declaring these yourself. Panel writes them inline on its root, which outranks a declaration on any ancestor. For content that should align with the Panel's padding after bleeding out of it, reach for `--bleed-px` instead. See [Reading container spacing](/foundations/spacing#reading-container-spacing). ## Accessibility diff --git a/docs/content/components/navigation/stepper/index.mdx b/docs/content/components/navigation/stepper/index.mdx index 7953f93228..650c1a52c8 100644 --- a/docs/content/components/navigation/stepper/index.mdx +++ b/docs/content/components/navigation/stepper/index.mdx @@ -140,7 +140,7 @@ Reach for `disabledKeys` when a step is visible but genuinely not available: one ### Links or buttons -When each step of your flow has its own URL, the steps should behave like the links they are, so middle-click, open in a new tab, and copy link all work. Give a **reachable** `` an `href` and it renders as a real link; a step that is still ahead stays inert text even with an `href`. Without an `href`, a reachable step renders as a button and only calls `onSelectionChange`. +When each step of your flow has its own URL, the steps should behave like the links they are, so middle-click, open in a new tab, and copy link all work. Give a **reachable** `` an `href` and it renders as a real link. A step that is still ahead stays inert text even with an `href`. Without an `href`, a reachable step renders as a button and only calls `onSelectionChange`. A bare `href` performs a real page load, which is usually not what a single-page app wants. Wrap the tree in a [``](/components/application/routerprovider) and hand it your router's navigate function: the steps stay real anchors for the browser, but clicking one routes client-side. With hrefs the route is the source of truth, so derive `selectedKey` from it and skip `onSelectionChange` entirely. diff --git a/docs/content/components/overlay/action-menu/index.mdx b/docs/content/components/overlay/action-menu/index.mdx index 684528e821..eb10fe0f0b 100644 --- a/docs/content/components/overlay/action-menu/index.mdx +++ b/docs/content/components/overlay/action-menu/index.mdx @@ -31,7 +31,7 @@ import { ActionMenuAnatomy } from './action-menu-anatomy'; ActionMenu's trigger is a slot-aware [`