Changelog
Every release, read from the CHANGELOG.md in the installed package. Breaking changes come first, each with what to change in your code. This site is on 1.2.1.
1.2.1
FixesFixes
npx shadcn addworks again. Since 1.1.1 the registry's theme item (registry/theme.json, which every component depends on) carriednullfor four tokens that only the portfolio brand defines:color-accent-glow,color-surface-spotlight,color-text-frozen-primaryandcolor-text-frozen-secondary. shadcn rejects anullcss variable, so installing any component from the registry failed with "Expected string, received null". Those four are now left out of the theme, the same waybase-light.cssandbase-dark.cssleave them out, and the theme's description counts the 137 tokens it carries. Only the registry changes; no component, token or CSS file does.
1.2.0
FeaturesFeatures
Five additions found while building the docs site, all non-breaking:
- Both brands on one page. New
styles/brands/portfolio-scoped.cssholds the portfolio overrides under[data-brand="portfolio"]instead of:root. Loadbase-light.cssandbase-dark.cssas usual plus this file, putdata-brand="portfolio"on a panel, and only that panel takes the portfolio brand. It follows the page'sdata-mode, or its own. Use it instead ofportfolio-light.cssandportfolio-dark.css, not alongside them. Those two files are unchanged, so nothing changes if you already use them. - Font links you can read from the package. New
tokens/fonts.jsongives the Google Fonts stylesheet link each brand needs: JetBrains Mono forbase, Schibsted Grotesk plus JetBrains Mono forportfolio, with the weights the tokens use. The package still ships no font files.llms.txtand the README have the same links. It's generated from the font tokens, so it changes when a font does. - Token descriptions. Every semantic and component token has a one-line
$description, andtokens/token-reference.json(andtokens.json) now carries it asdescriptionon each entry,nullwhere there isn't one. Before, the field never reached the reference at all. - MCP tools in
llms.txt.llms.txtandllms-full.txtlist the MCP server's tools, generated from the server itself, so the list matches what it serves. - Doc twins show base values. The Tokens tables in
docs/components/*.mdnow show thebasetheme's values. They showed the portfolio brand's, so Button's secondary colours read#292524instead of base's#262626.
- Both brands on one page. New
1.1.2
FixesFixes
Link,SkipLinkandTagnow start with'use client'. They use client-only React (RadixSlotin Link, click handlers in SkipLink and a removable Tag) without saying so, so a Next.js App Router consumer couldn't render them from a Server Component: the build failed withcreateContext is not a functionor "Event handlers cannot be passed to Client Component props". The docs site hit it first and worked around it with a client re-export, which it can now delete. Nothing changes in the browser or for consumers outside the App Router.npm run validategainscheck-client-directives.mjs, which fails any component that uses state or effect hooks, context, Radix or an inline event handler without the directive. The tests and Storybook render in a browser, where the directive does nothing, which is how these three shipped without it.
1.1.1
FixesFixes
Ship the agent-facing files in the package:
tokens.json,skills/(the agent skill and itsindex.json),registry/(the shadcn-spec registry manifests) anddocs/components/(the per-component Markdown docs). The docs site (design-system-site) generates its/.well-known/skills/, registry and doc pages from the installed package at build time, instead of from hand-copied files. The URLs inside these files still point at amezquita.dk and move when the docs site's subdomain is live.Fix: the shadcn registry's
cssVarsnow carry thebasetheme. They were resolved from the portfolio brand, sonpx shadcn addinstalled the portfolio's colours instead of the brand-neutral default every consumer is meant to start from. Anyone who installed components through the registry and relied on those colours should import the portfolio brand CSS or set their own values. Consumers who install the npm package and import the brand CSS are unaffected.
1.1.0
FeaturesFeatures
Five navigation components (
decisions/0015) and breakpoint tokens (decisions/0018).Link(primitive): use it for navigation, and Button for actions.variant="inline"(underlined, for running text) or"standalone"(underline on hover and focus).externalopens a new tab and says so to screen readers.asChildrenders your router's link:<Link asChild><NextLink href="/about">About</NextLink></Link>.SkipLink(primitive): put it first in<body>andid="main-content"on your<main>. It shows on the first Tab and moves focus into the main content. Stacks with the new--z-skip-linktoken.Menu(composition): a dropdown of actions from atrigger, in groups, with disabled and destructive items. Opens correctly inside a Dialog or Drawer.NavigationMenu(composition): the header navigation, from 1024px up. Links and groups that open a dropdown of links. TakescurrentHrefforaria-current="page"andLinkComponentfor your router.SideNav(pattern), withSideNavProviderandSideNavTrigger: section navigation inline beside the content from 1024px up, and in a left Drawer opened fromSideNavTriggerbelow it.headerItemsputs NavigationMenu's links in that drawer too, so a site with a header nav gets a mobile menu withlayout="drawer-only".collapsedturns the inline nav into an icon rail. TheNavItemtype both components take is exported from each of their folders.Breakpoint tokens:
breakpoint.tablet(768px) andbreakpoint.desktop(1024px), emitted as--breakpoint-tabletand--breakpoint-desktop. Custom properties can't be read in@media, so write the literal value, mobile-first:@media (min-width: 1024px). For JS,lib/breakpoints.tsexportsBREAKPOINTSandmediaQuery('desktop').One visible change to an existing component: Card's description used to hide below 1200px and now hides below 1024px, the nearest breakpoint token. Between 1024px and 1199px it now shows.
New dependencies:
@radix-ui/react-dropdown-menu,@radix-ui/react-navigation-menu, and@radix-ui/react-slot(already installed with the other Radix packages, now declared directly).
1.0.0
Breaking changesFixesBreaking changes
Button is functional by default, and the expressive hover is opt-in (
decisions/0016). Every Button changes on upgrade.Hover: a plain background change over a short CSS transition — the rules that previously only reached people with
prefers-reduced-motion: reduce. The GSAP wipe and cursor-following glow are now behindmotion="expressive".The arrow is opt-in.
noArrowis gone; usearrow:<Button noArrow>→<Button><Button>(was getting an arrow) →<Button arrow>
To keep the previous look, set both:
<Button motion="expressive" arrow>. A wrapper in your own app is the tidier place for that than repeating it at every call site.GSAP now loads through a dynamic
import()on the expressive path, so it never executes in an app that doesn't use it. It remains a regular dependency rather than an optional peer — this package ships raw source with no build step, so the import has to stay resolvable.Tokens:
--button-glow-coloris now a real component token instead of a hardcoded value inButton.css, so a brand can override it.Unchanged:
motion="expressive"still does nothing underprefers-reduced-motion: reduceor on a device without hover, where it falls back to the functional hover.Removed:
hooks/useButtonWipe.ts. Nothing in this package used it. It imported GSAP statically, so importing it pulled GSAP into your bundle whatever Button'smotionprop said, and its default fill pointed at--button-primary-background-hover, a token removed in an earlier release. If you imported it directly, copy it into your own app; the expressive wipe on Button itself is unaffected.
Fixes
- The package now ships its agent and reference docs alongside the code:
llms.txt,llms-full.txt,AGENTS.md,CHANGELOG.md, and the ADRs indecisions/*.md(decisions/0017). An agent working in a consumer project can read them fromnode_modules/@amezquita/design-system/, and the docs site builds its Changelog, Working with AI and Decisions pages from the published package instead of from this repo. No code or token changes.
0.6.0
FeaturesFeatures
This release reworks the type scale and adds container widths. It removes tokens, so read Removed before upgrading.
5c061df, cff0463, 797c753: Line-heights on the 4px grid, and a simpler type scale (
decisions/0012,decisions/0013). Every static font size now has a fixed pixel line-height on the same 4px grid as the spacing scale. The static scale goes from 13 sizes to 9:Role Size / line-height h164 / 80px h248 / 60px h332 / 40px h424 / 32px h520 / 24px body18 / 28px control16 / 24px small14 / 20px (form labels: 14 / 16px with --line-height-label)caption(new)12 / 16px Fluid
displayandh1–h3keep their unitless ratios (--line-height-display,--line-height-heading).--line-height-bodyis now a fixed28px, not1.5. It no longer scales with the element's font size: text inheriting it from<body>gets 28px whatever its size. Pair each font size with its own line-height role instead (--font-size-smallwith--line-height-small, and so on).Removed, with replacements:
--font-size-micro→--font-size-caption(10px text becomes 12px)--font-size-h6→--font-size-h5/--line-height-h5, or--font-size-bodyif you need 18px--font-size-label→--font-size-small(same 14px; keep--line-height-labelfor one-line labels)--font-size-lead→--font-size-h4/--line-height-h4(same 24 / 32px)--tooltip-font-size,--input-hint-size,--textarea-hint-size,--table-header-font-size,--badge-font-size,--tag-font-size→--font-size-caption--line-height-normal(1.5) and--line-height-loose(1.75) primitives, and thefont-size.2xs(10px) primitive: set the value in your own CSS if you used them
Heading:levelis now1–5.level={6}no longer type-checks; uselevel={5}, addingas="h6"if you need a real<h6>.Visible changes:
- Tooltip, Input and Textarea hints, and Table header/foot/caption text: line-height 20px → 16px.
--font-size-h2-fluidis nowclamp(1.625rem, 1rem + 2.5vw, 3rem)(26px → 48px, was 22px → 48px). Fluid H2 is up to 4px larger below about 1280px wide, and never smaller than H3 (before, it was smaller at every width under 800px).- EmptyState's compact title: 18 / 24px → 20 / 24px.
- Dialog's title resolves to
--font-size-h4/--line-height-h4directly; the.heading.dialog__titleoverride is gone. Same size as before. - Most other static text moves by 1–2px of line-height.
Container width scale (
decisions/0014):--size-container-text(45rem),--size-container-media(60rem),--size-container-wide(80rem),--size-container-page(90rem) and--size-container-site(90vw, applied from 1024px up; below that the site is 100% wide with--space-layout-margingutters). Use them directly asmax-width; as awidthor grid track, wrap them inmin(var(--size-container-*), 100%)so they fill narrow screens.--space-layout-max-widthis deprecated in favour of this scale. It keeps its 1200px value in this release.
0.5.0
FeaturesFixesFeatures
Added
Heading, a new primitive renderingH1–H6via alevelprop (visual size) decoupled from anasprop (real semantic tag, defaults to matchinglevel) — so a heading's visual size and its place in the document outline can diverge deliberately (e.g. a visually smallH1that stays a real<h1>for SEO/screen-reader navigation).H1–H3use fluid,clamp()-based sizing (font-size-h1-fluid, and the previously-defined-but-unusedfont-size-h2-fluid/font-size-h3-fluid, now wired in for the first time).H4–H6are deliberately static:H4reuses the existingfont-size-h4;H5andH6get two new static semantic tokens,font-size-h5(20px) andfont-size-h6(18px). The fluid/static split follows the pattern every major production design system (IBM Carbon, Material 3, GitHub Primer, Adobe Spectrum, Shopify Polaris) already uses — fluid scaling reserved for large/prominent display text, dense UI chrome stays static — recorded indecisions/0008-productive-expressive-typography-split.md, along with confirmation that all four fluid tokens already meet the WCAG 1.4.4 200%-zoom-safe bar (max/min ratio ≤ 2.5×, mixed rem+vw preferred values).Card,Dialog,Drawer, andEmptyStatecomponent titles keep their existing static sizing under this split — that was already correct, not something this release "fixes." (Their internal implementation was later migrated onto this primitive; see the separate changeset for that.)No breaking changes. No existing component's rendered output changes.
Fixes
CardTitle,Dialog's andDrawer's title, andEmptyState's title now render via theHeadingprimitive internally instead of each maintaining its own parallel heading CSS/markup — closing the gapdecisions/0008deliberately left open as a follow-up. No public API change and no visual change: every affected title was verified byte-identical before/after (computed styles and rendered pixels), andCardTitle/EmptyState's existingas/levelprops behave exactly as before.Fixes a real bug found while migrating:
DialogandDrawer's registry manifests (registry/dialog.json,registry/drawer.json) were missingheading.jsonas aregistryDependency, because the manifest generator only traced a component's own direct imports and both composeHeadingone hop away, through the internalBaseSheetcomponent. A consumer installingDialogorDrawervia the shadcn-style registry CLI would get source that importsHeadingwithout ever fetching it. The generator now follows imports through internal siblings.
0.4.0
FeaturesFeatures
Expanded the primitive
spacescale from 10 to 13 steps (4/8/12/16/20/24/28/32/40/48/64/96/128,space.1–space.13), filling gaps the old scale had past 16px (no 20px, no 28px step). Every existing token reference was repointed to the new step holding the same pixel value — this is non-breaking, no resolved value changes for existing usages.Added three new semantic tokens —
space-dialog-padding-mobile,space-dialog-padding-tablet,space-dialog-padding-desktop(16px/24px/32px) — and madeDialog's content padding responsive via a real@mediaquery (768px/1024px breakpoints) switching between them, replacing the previous single static padding value.
0.3.2
FixesFixes
Every primitive now forwards a ref, spreads unrecognised props onto its rendered element, and merges a consumer's
classNameinstead of discarding it. FixesSelect's dropdown rendering behind its own containingDialog.- Button, Textarea, Badge, Checkbox, Input all extend the real native element's (or, for
Checkbox, the real Radix primitive's) own props type and spread...restonto the rendered element —onKeyDown,onBlur, arbitrarydata-*/aria-*attributes, and third-party libraries that spread their own props onto a rendered node (e.g.@dnd-kit/sortable'sattributes/listeners) now all reach the real DOM node. Previously onlyButtonhad this (decisions/0006); this extends the same pattern to every other primitive with a single rendered element. classNameis now merged, not silently overwritten, on all five of the above — includingButton, whichdecisions/0006's fix didn't close: it spread...rest(which carriesclassName) but then wrote its own computedclassNameafter it, unconditionally overwriting whatever a consumer passed.Buttonnow forwards Radix'sasChild-injected ARIA contract and an externalclassName— both needed forButton/Badgeto work correctly as aDialog/AlertDialogtrigger with consumer-supplied styling.Select's dropdown no longer renders behind aDialogit's nested inside, andDrawer's content/overlay no longer outrankDialog/AlertDialog.Select.css,Dialog.css, andDrawer.cssno longer set an explicitz-indexon their overlay/content — all now layer by DOM mount order instead of a fixeddropdown < overlay < modalrank, the same fix Radix's own maintainers shipped for the identical bug.--z-dropdown,--z-overlay, and--z-modalare now unreferenced (not removed from the token files in this release).AlertDialog'scancel/actionslots can now safely useButtoninstead of a plain<button>— the guidance recommending a raw element there is retired;Button's ownasChild-composition contract test (Button.slot.test.tsx) now covers this.Badge,Checkbox,Textarea, andInputeach explicitly reject one or two props they can't actually support, caught by review rather than shipped silently broken:Badgeno longer acceptsrole(it always computes its own — a deliberate a11y decision, not a passthrough gap);Checkboxno longer acceptschildren/asChild(its check/indeterminate icon is fixed markup);Textarea/Inputno longer acceptchildren(both are controlled viavalue, and<input>is a void element besides — passingchildrento either was always going to misbehave, this just makes the type say so instead of admitting it silently).
No breaking changes for any real existing usage — the three components above were never designed to accept
role/children/asChildin the first place; nothing that worked before stops working. No component removed or renamed.- Button, Textarea, Badge, Checkbox, Input all extend the real native element's (or, for
0.3.1
FixesFixes
Fix Button not forwarding a ref, fix a dark-mode-only textarea border mismatch, and correct 17 component tokens with an inaccurate
$type.- Button now forwards a ref via
React.forwardRefto its underlying<button>or<a>element — needed for anything Radix clones a ref onto viaasChild(e.g. AlertDialog'striggerRefrestoring focus to a Button trigger). - Textarea border color in dark mode (
basebrand) was overridden to a different neutral-scale step thancolor-border-default, causing it to render a visibly different border from every other control in dark mode only. Removed the stray override so it inherits likeinput-borderalready does. - Collapsed 90 component-token chain-skips into 9 new semantic roles (
space-control-padding-*,space-prominent-padding-*,space-compact-padding-*,space-container-padding*,font-weight-control) and adopted 2 existing-but-unused roles (border-radius-pill,border-radius-interactive) more broadly — reduces the token architecture's chain-skip count from 164 to 74 with zero visual regressions. - Fixed
font-weight-labelrole, which was set tofont-weight.semiboldbut every label component actually usedfont-weight.medium— the role existed but was unused and wrong. - Fixed
tabs-indicator-height, which was typed"color"but held a dimension value. - Corrected 17 tokens across
avatar,spinner,skeleton,dialog,drawer,toast,tooltip, andaccordionfrom$type: "other"to their accurate DTCG type (dimension,color,number, orduration) — matters beyond metadata correctness sincesd.config.mjs'ssize/remtransform filters on$type.
No breaking changes. No component API changes besides the additive Button ref.
npm run validateandtokens:auditgreen throughout — chain-skip 164 → 74, zero regressions.- Button now forwards a ref via
0.3.0
FeaturesFeatures
Add
AlertDialog— a confirmation gate for actions the user must explicitly accept or decline, distinct fromDialog: realalertdialogrole, no outside-click dismiss, no free-floating close button, forces a Cancel/Action choice. Built on@radix-ui/react-alert-dialog(new dependency); reusesDialog's own CSS classes and tokens rather than duplicating them, since visually it's the same box with a different behavioral contract underneath.Also extends
Dialogwith an optionaltriggerRefprop, merged with its existing internal one — lets a consumer keep a handle on the trigger element to restore focus there manually after a separate follow-upAlertDialogcloses (a cross-dialog focus-restore caseDialog's own internal focus-restore hook doesn't cover, since it only knows about itself). Non-breaking addition.
0.2.0
FeaturesFeatures
tokens/token-reference.jsonnow resolves every global primitive, not just thecolor.*group — 83 previously-invisible primitives (spacing, type scale, radii, motion, shadows, sizes,feedback.*colours) are now included. Total goes from 537 to 620 tokens (101 primitive, 114 semantic, 405 component).Breaking, within this field:
meta.tokenCountis renamed tometa.total, and now sits alongsidemeta.primitiveCount,meta.semanticCount, andmeta.componentCount. Any consumer readingtokenReference.meta.tokenCountdirectly needs to switch tometa.total.tokens/component-registry.jsongains aninternalflag per component (set on components that ship in the package because something else imports them, but aren't meant to be used directly — currently justBaseSheet) and ameta.publicComponentCountfield alongside the existingmeta.componentCount.No changes to component source, styles, or hooks — this release only touches the two generated token/registry JSON files.
0.1.4
FixesFixes
Ship the token artifacts with the package.
tokens/is now included infiles, so consumers and coding agents get the DTCG token source ($value/$type), the resolved token reference, and the component registry on install — previously these existed only in the repo and reached nobody who installed the package.First step of the AI-readiness roadmap (
docs/ai-readiness-plan.md, Task 1.0).
0.1.3
FixesFixes
- fix: convert remaining internal @/ imports to relative paths
0.1.2
FixesFixes
- fix: resolve internal @/ imports to relative paths in Button, EmptyState
0.1.1
FixesFixes
- Verify npm trusted publishing pipeline end-to-end