Skip to content

feat(docs-next): convert legacy docs pages + doc grid and brand/copy polish#2239

Draft
dancormier wants to merge 240 commits intomainfrom
STACKS-843/legacy-header-extraction
Draft

feat(docs-next): convert legacy docs pages + doc grid and brand/copy polish#2239
dancormier wants to merge 240 commits intomainfrom
STACKS-843/legacy-header-extraction

Conversation

@dancormier
Copy link
Copy Markdown
Contributor

@dancormier dancormier commented Apr 14, 2026

Summary

Follow-up cleanup work after the alpha/beta/v2 branch consolidation (STACKS-843). This PR migrates legacy HTML documentation to clean mdsvex, overhauls the page shell and layout, standardizes component and element usage throughout, and polishes many docs pages.

Related issue

STACKS-843 — Perform follow up tasks for the alpha, beta, and v2 branch consolidation

What changed

Page shell & layout

  • Replaced fixed-position nav with a three-column flex layout: left nav | content | table of contents
  • ToC is sticky, proportionally sized, and hidden on pages with no headings
  • Hero images applied as background-size: cover for responsive scaling; gap added above them
  • Page header includes breadcrumb, functional copy-link button, and Figma/Svelte/JS links where relevant
  • Unified heading sizes, section spacing, and body text styling across all pages

Component and legacy page conversions

  • Converted all remaining legacy HTML component pages to mdsvex using ClassTable, Example, and @stackoverflow/stacks-svelte components: navigation, notices, pagination, popovers, post-summary, prose, sidebar-widgets, tables, tags, toggle-switch, user-cards, vote
  • Added stub pages for 9 components removed in Stacks v3 (breadcrumbs, button groups, cards, etc.) that link back to v2 docs
  • Merged form component pages under the Components section
  • Fixed all Figma, Svelte component, and JavaScript section links

Resources — Icons & Spots

  • Fixed the icons/spots page rendering in a narrow column by ensuring its layout fills the full available width

How to review

For engineers

  1. Run the dev server with npm run start or use the Netlify deploy preview linked on this PR
  2. Walk through the pages below and check for broken examples, incorrect layout, or missing content
  3. Please mention any issue you find noting if it should block merging

For @abovedave

Use the Netlify deploy preview to make sure all Brand and Copywriting docs pages look and work as expected. If anything looks off — wrong text, missing content, layout problems — please leave a comment on this PR with a screenshot and the URL of the page.

Legacy fragments each start with a page-header div
(d-flex ai-start jc-space-between) containing the description paragraph
and action badges (Svelte, Figma). This div was built for the old docs
template, which expected it as part of the fragment. The new template
provides its own header, causing the description (and action links) to
appear inside the content area rather than in the header.

Add extractLegacyHeader() which:
- Finds the opening header div and locates its matching closing tag via
  a depth-counting walk (handles nested divs robustly)
- Extracts description text from the .docs-copy <p>
- Extracts Svelte and Figma action URLs from the badge <a> hrefs
- Returns the fragment HTML with the header div removed

The extracted values are returned as metadata so the new template renders
description and action buttons consistently with MD-sourced pages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@dancormier dancormier requested a review from a team April 14, 2026 23:30
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 14, 2026

⚠️ No Changeset found

Latest commit: 0574d68

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@netlify
Copy link
Copy Markdown

netlify bot commented Apr 14, 2026

Deploy Preview for stacks ready!

Name Link
🔨 Latest commit 0574d68
🔍 Latest deploy log https://app.netlify.com/projects/stacks/deploys/69e694c281525d0008840321
😎 Deploy Preview https://deploy-preview-2239--stacks.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@dancormier dancormier marked this pull request as draft April 14, 2026 23:31
@abovedave
Copy link
Copy Markdown
Collaborator

abovedave commented Apr 15, 2026

@dancormier you could potentially simplify this by getting 11ty to output in an easier to work with format like using JSON.parse on a string put into the page itself. Might also work for the table of contents too. Redundant!

dancormier and others added 24 commits April 15, 2026 11:29
The Figma badge on some pages (e.g. color-fundamentals) is hosted on
svelte.stackoverflow.design (/figma/colors) rather than figma.com.
URL-domain matching would incorrectly classify those links as Svelte.

Replace domain-based regexes with per-badge <a>...</a> matching: iterate
each badge element and identify it by the text label that follows the SVG
icon (Svelte or Figma), not by the URL.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
8 legacy fragments have Figma badges linking to
svelte.stackoverflow.design/figma/* aliases instead of actual
figma.com URLs (e.g. color-fundamentals, typography, buttons).

Filter extraction to only accept figma.com URLs for the Figma button.
Those 8 pages will show no Figma button until correct figma.com URLs
are added to their fragments or to structure.yaml.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ages

Root cause: the stacks-docs page.html Eleventy template was rendering
the page description twice — once inside the header flex div and once
as a standalone paragraph below it. When fragments were (re)built, both
occurrences appeared in the HTML, causing two descriptions to show.

Changes:
- stacks-docs: remove the duplicate standalone description paragraph
  from page.html (the description inside the flex div is sufficient;
  the new template extracts it as metadata and renders it in the header)
- stacks-docs-next: add a belt-and-suspenders strip in extractLegacyHeader
  to also remove any standalone description paragraph left over in
  already-built fragments
- stacks-docs: replace 7 wrong Figma badge URLs that pointed to
  svelte.stackoverflow.design/figma/* aliases with correct figma.com
  project links (color-fundamentals, icons, typography, spots, buttons,
  tags, editor)
- stacks-docs: remove figma: field from box-shadow (no Figma link intended)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
stacks-docs-next was built on top of stacks-docs, copying pre-built
HTML fragments, compiled assets, and a JS bundle from stacks-docs/_site
at build/dev time via scripts/copy-legacy.mjs.

Since stacks-docs-next is intended to replace stacks-docs, this removes
the runtime coupling:

- Commit static/legacy/ directly — the 98 legacy fragment HTML files,
  compiled assets (docs.css, entry bundles, images), docs.js, and
  llms.txt are now committed as static content rather than being
  generated by copying from stacks-docs
- Remove scripts/copy-legacy.mjs — no longer needed
- Remove predev script — was only used to invoke copy-legacy.mjs
- Remove stacks-docs build step from prebuild — prebuild now only
  builds stacks and stacks-svelte, which stacks-docs-next directly
  depends on
- Remove unused $data alias from svelte.config.js — pointed to
  ../stacks-docs/_data but was never imported anywhere

The static/legacy/ content is now a committed snapshot of the legacy
docs. Individual fragment files can be deleted as their pages are
migrated to stacks-docs-next; assets and docs.js can be cleaned up
once all legacy pages have been replaced.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
These changes (page.html template fix, frontmatter Figma URL updates)
belong in a separate stacks-docs PR. This branch covers stacks-docs-next
only. The duplicate-description and Figma URL concerns are handled in
stacks-docs-next via extractLegacyHeader and the committed fragment files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes 88 files (~10 MB) from static/legacy/ that are not referenced
by stacks-docs-next or the fragment HTML files:

- assets/dist/ (entire directory, ~8.1 MB)
  - docs.css — not loaded by stacks-docs-next or any fragment
  - docs.js.LICENSE.txt — build artifact, not served
  - entry.stacks-editor.js — 6 MB bundle, not loaded
  - all other entry.*.js files — not loaded by stacks-docs-next

- assets/img/ (76 unreferenced images, ~2 MB)
  - logos/ — entire brand logos directory (56 files)
  - icons/ — logo glyph SVGs (6 files)
  - favicons/ — favicon files (3 files)
  - various root-level images not referenced by any fragment

Remaining in assets/img/: the 10 images actually used by fragments
(avatar and email preview assets).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces 38 static legacy HTML fragments with mdsvex Markdown files in
src/docs/public/system/, served natively by the SvelteKit page server.

Converted pages (base/ and components/ and foundation/):
- base/height, margin, padding, width
- components/activity-indicator, avatars, badges, bling, breadcrumbs,
  button-groups, cards, code-blocks, empty-states, expandable, inputs,
  labels, link-previews, links, loader, menus, page-titles, pagination,
  post-summary, progress-bars, radio, select, tables, tags, textarea,
  toggle-switch, topbar, uploader, user-cards, vote
- foundation/accessibility, color-fundamentals, colors, typography

Each MD file has YAML frontmatter (title, description, svelte URL,
figma URL where available) extracted from the legacy fragment header,
with the content HTML following.

Also removed the `legacy:` field from structure.yaml for all 67 pages
(38 newly converted + 29 that already had MD files but were being
bypassed by the legacy-first serving order).

10 pages remain as legacy fragments because they contain inline
<script> demos that require manual conversion to Svelte components:
banners, buttons (already has full index.md), checkbox, editor,
modals, navigation, notices, popovers, prose, sidebar-widgets, theming.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…r to mdsvex

Establishes the pattern for converting legacy HTML docs pages to clean,
maintainable mdsvex.

**ClassTable.svelte** — generic CSS class documentation table:
- Auto-detects which columns to show based on the data (no config needed)
- Supports both component pages (modifies/description) and base utility
  pages (output/define/responsive) with the same component
- Exports ClassTableRow type for use in page script blocks

**activity-indicator.md** — prototype of the new page format:
- 588 lines → 92 lines
- Uses the actual ActivityIndicator Svelte component for live demos
  instead of pre-rendered HTML
- Uses ClassTable with typed class data
- Uses standard markdown headings (processed by rehype-slug +
  rehype-autolink-headings — no custom heading component needed)
- Uses fenced code blocks for HTML usage examples

The same pattern applies to all other converted pages:
1. Import ClassTable + the relevant stacks-svelte component
2. Define class data as a typed array in the script block
3. Use markdown headings + prose
4. Render live demos with Svelte components
5. Show HTML class usage in fenced code blocks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…a11y

- Prop was named 'rows' but usage passes '{classes}', causing rows to be
  undefined on render
- Added role="region" + aria-label to the scrollable wrapper so tabindex
  on a non-interactive element is valid

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… order

- Code block first, then live example (matching original layout)
- Restore all compound examples:
  - Standalone indicators (empty, numbers, text)
  - Activity indicator on avatar (ActivityIndicator + Icon + raw s-avatar HTML)
  - Activity indicator on notification icon (Icon + ActivityIndicator with positioning classes)
  - Activity indicator count pill on notification icon
- Use {#each} over variants to eliminate repetition in the Variations section
- Replace @svg.* C# syntax in code blocks with commented HTML equivalents

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…kage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Example.svelte wraps live demos in a light gray bordered container
with padding, keeping examples visually distinct from prose and code
blocks. Used in activity-indicator.md as the pattern to follow.

Also adds TODO comments in both the script block and the two avatar
examples noting where IconShieldXSm should be added once the icon
is published in @stackoverflow/stacks-icons.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Changes rehype-autolink-headings from behavior:"wrap" (which made the
heading text itself a silent link) to behavior:"append" (which appends
the IconLink SVG after the heading text).

Pairs with CSS in app.css that makes h2/h3/h4 flex containers so the
icon is pushed to the right edge, matching the original legacy layout.
The anchor is aria-hidden so screen readers aren't confused by the
duplicate link — the heading id is the accessible anchor.

This is a site-wide fix that works automatically for all mdsvex pages
without needing a custom heading component.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…in breadcrumb

Removes the "Copy" button from the page actions row and adds a small
link icon button (s-btn__xs s-btn__clear s-btn__icon) inline at the end
of the breadcrumb nav. Clicking it copies the current page URL to the
clipboard using the existing copyToClipboard action.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…button

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…b copy link

- Replace raw <button> with <Button link icon> using the link prop
- Handle clipboard write with an inline async onclick instead of the
  use:copyToClipboard action (actions can't be applied to components)
- Size icon with w16 h16 Stacks atomic classes — removes the custom CSS
  rule and the .breadcrumb-copy-link class
- Remove unused copyToClipboard import and copysuccess window listener

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove flex--item from nav; nav is already a flex child via the outer div
- Make nav itself d-flex ai-center g6 so items are evenly spaced and
  vertically aligned, including the copy link button
- Remove pr6/mr6 from individual items — gap handles all spacing
- Move separator before each item (index !== 0) instead of after,
  which is cleaner with a flex gap layout
- Remove ml4 from Button — gap handles the spacing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds @stackoverflow/stacks-icons-legacy as a dependency (workspace *).
IconShieldXSm lives there rather than in the main stacks-icons package.
Restores the shield badge icon to both the Default and Variations avatar
examples in activity-indicator.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… reordering to top

Replaces md:order-first (which moved the TOC to the top of the page in
column layout) with md:d-none (hides it entirely). Also removes the now-
unused collapsible toggle button, isOpen state, and related Icon imports.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
dancormier and others added 22 commits April 17, 2026 13:07
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wrap main and Contents in a layout-content column so the banner image
sits above both. Apply mt24 gap conditionally on the content row so it
only appears on pages without a banner.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When toc is empty the aside was still occupying its w20 column. Moving
the #if outside the aside removes it from the DOM entirely, letting main
fill the remaining space.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fl-grow1 was expanding main to fill all remaining space, pushing the ToC
to the far right edge. Without it, main sizes to its content (capped at
wmx9 by the inner doc div) and the ToC sits immediately to its right.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
main and the ToC aside no longer carry bg-white individually. The
layout-body wrapper (fl-grow1) owns it so the white background fills
the entire area to the right of the nav regardless of content width.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces wmn1 wmx2 w100 — the 256px max was too narrow on larger
viewports. w20 scales with the content area; wmn2/wmx3 constrain
the extremes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Moves width off the CSS rule and onto the element using Stacks classes,
matching the same approach as the ToC column.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… CSS

ps-sticky t0 h-screen and the sm: mobile resets are now on the element.
sm:fd-column moves to the layout-root div. The .layout-nav rule and its
media query are deleted entirely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
flex-grow: 1 on main makes it fill the full layout-body height and width
when no ToC is present. justify-content: center vertically centers the
page content. wmx12 caps the content width on very wide viewports.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The children wrapper div in the layout needed d-flex fd-column so that
my-auto on the home page's .page div can distribute equal top/bottom
margins. justify-content: center on main was targeting the wrong level.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Convert all legacy HTML base utility pages to use ClassTable, Example,
and Notice components. Fix mobile nav scrolling, improve ClassTable
accessibility, and add consistent anchor classes to all notices.

- Convert 20 base pages (border-radius, box-shadow, box-sizing,
  current-color, cursors, display, flex, floats, gap, grid, height,
  interactivity, lists, margin, object-fit, opacity, outline, overflow,
  padding, position, transitions, truncation, vertical-alignment,
  visibility, width, z-index) from legacy HTML to ClassTableRow arrays
  with ClassTable + Example components
- Fix mobile nav: position: fixed overlay + body scroll lock on menu open
- ClassTable: add hover/focus/responsive/print boolean fields with
  accessible icon checkmarks (SVG title instead of aria-label); reorder
  columns; add code styling
- Example: remove bar-sm border radius from all example containers
- Add s-anchors classes to all Notice components outside of notices.md
- Fix stacks-code prose in flex.md and other cleanup

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Convert color-fundamentals, colors, and typography from legacy HTML
  to clean mdsvex — strip section wrappers, legacy heading blocks,
  docs-copy classes, and Liquid template artifacts
- Add typography ClassTable arrays (basicClasses, layoutClasses)
- Create theming.md from legacy source; stub interactive HSL playground
  with a TODO comment and coming-soon notice
- Remove orphaned foundation/accessibility.md (not in nav)
- Remove legacy: key from theming entry in structure.yaml

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Delete unused webpack bundles (docs.js, legacy-docs.js), 8 unreferenced
images, and a stale llms.txt artifact from the old site. Remove
commented-out preview banner from +layout.svelte.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BannerExample → BannerPreview (static pre-built instance)
ExampleTable  → PreviewTable  (table of component previews)
ExampleTableRow → PreviewTableRow (exported type)

Establishes a clear naming convention: Example = generic wrapper,
*Preview = static component instance, *Demo = interactive playground.
Updated CLAUDE.md with the convention for future reference.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the writable-store-based column counting in Grid/GridColumn
with a plain CSS grid layout. The store caused `grid__0` to render on
the initial SSR pass because children hadn't updated the count yet.

The new layout uses `repeat(auto-fit, minmax(min(220px, 100%), 1fr))`
for equal-width, gap-consistent columns, with a single-column breakpoint
at the sm: breakpoint (48.75rem) for mobile stacking.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- layout.svelte: add mt24 above hero images so they don't butt against
  the page header
- ColorHeadlines: fix Navigation margin (mbn24 → mb8)
- ColorCodes: fix horizontal overflow — switch grid columns from
  `repeat(6, 1fr)` to `repeat(6, minmax(0, 1fr))` so swatches shrink
  to fit the container; remove overflow-auto
- TypographyWeights / TypographyCharacter: remove ws-nowrap from
  Navigation; add mb8 spacing below the tab bar
- TypographyCharacter: replace non-existent bg-brand-orange utility
  with inline style using the --brand-color-orange CSS variable
- TypographyNotch: same fix for bg-brand-beige → --brand-color-beige
- brand/typography/index.md: add mt16 gap between TypographyWeights
  demo and the Grid below it

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
docs-copy styling:
- naming.md, styleguide.md, messages.md: add docs-copy class to all
  <ul> elements inside Grid/GridColumn slots (rehype only applies it to
  markdown-level elements, not HTML in Svelte slots)

settings.md:
- Replace raw <input class="s-checkbox"> markup with Checkbox Svelte
  component
- Replace raw <input class="s-input"> markup with TextInput Svelte
  component (with {#snippet description()} for helper text)

voice.md:
- Add docs-copy to all <p> elements inside GridColumn slots
- Add scoped <style> to bring <h1> from the global fs-display3 (72px)
  down to fs-headline1 (28px) — the global rule is designed for page
  titles, not inline copy example blocks

accessibility/alt.md:
- Convert inline s-notice div to <Notice> Svelte component
- Wrap example boxes in <Example>

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@dancormier dancormier changed the title feat(docs-next): convert legacy component docs to mdsvex feat(docs-next): convert legacy docs pages + doc grid and brand/copy polish Apr 20, 2026
dancormier and others added 2 commits April 20, 2026 14:29
Remove the legacy conversion checklist (that work is now complete) and
replace with forward-focused guidance: page anatomy, adding new pages,
doc component reference, Grid/GridColumn usage, and key gotchas
(docs-copy in Svelte slots, Notice p-tag rule, global h1 sizing,
missing bg-brand-* utility classes).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add fullWidth/hideToc page data flags; layout applies w100 to <main>
  and suppresses the Contents TOC sidebar on the icons page only
- Hide the icon inspector aside on all screen sizes when nothing is selected

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@dancormier dancormier requested a review from abovedave April 20, 2026 20:05
dancormier and others added 4 commits April 20, 2026 16:26
stacks-editor and stacks-icons-legacy were added to package.json in
previous commits but the lockfile workspace metadata was not updated,
causing npm ci to fail on Netlify.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rollup (used by Vite for production builds) cannot statically extract
named exports from webpack's UMD bundle format, causing
"hidePopover is not exported" when stacks-editor's ESM source imports
from @stackoverflow/stacks.

- Add lib/esm-no-css.ts: an ESM entry that mirrors lib/index.ts but
  omits the CSS side-effect (the docs site imports the CSS directly
  via app.css)
- Alias @stackoverflow/stacks to this entry in vite.config.ts (exact
  match via regex; subpath imports like /dist/css/stacks.css are
  unaffected)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants