Files
Junior Garcia cb3a171955 V3.2.6 (#6850)
* build(deps): patch js-yaml and svgo security advisories (#6844)

Pin transitive js-yaml and svgo to patched versions via pnpm overrides:

- js-yaml 3.15.1 -> 3.15.2 and 4.3.1 -> 4.3.2 (GHSA-2883-xcg3-v3hh):
  maxTotalMergeKeys does not count empty mappings, allowing CPU
  exhaustion via repeated merges of a large empty-mapping sequence.
- svgo 2.8.3 -> 2.8.4 (GHSA-w27v-7q3p-w38r, GHSA-4vpr-x523-8j87):
  removeScripts let executable links through via namespace-prefixed
  anchors and control-character URL schemes, and did not sanitize HTML
  inside foreignObject.

All three patches satisfy the ranges their parents already declare
(gray-matter ^3.13.1, postcss-svgo ^2.7.0), so no package.json changes
are needed. Overrides can be dropped once those parents update.

* fix(styles): only ring list-box and menu items on real keyboard focus (#6849)

* fix(styles): only ring list-box and menu items on real keyboard focus

v3.2.5 dropped `:not(:focus)` from the focus rules repo-wide. That guard
made the native selector inert, so the ring had only ever been driven by
React Aria's data-focus-visible. Reinstating it as-is keeps dead code, so
scope the native fallback to elements React Aria does not own instead.

* fix(styles): extend keyboard-only focus ring guard to swept selectors

The ring also appears when React Aria restores focus to a trigger on
overlay close, not just when focus moves into a collection on open.

Enumerating affected surfaces proved unreliable, so apply the guard
wherever the v3.2.5 sweep landed: 37 selectors across 26 files. React
Aria sets data-focused whenever it owns an element, so the attribute
path covers it fully and the native path stays a non-RAC fallback.

Calendar nav buttons keep their bare :focus-visible since they predate
the sweep. Input and textarea focus-visible blocks are empty; their
ring comes from :focus and is unchanged.

* build(deps): bump react-aria to 3.52.1 and react-aria-components to 1.21.1 (#6848)

* chore(styles): fix selective-import recipe and size measurement (#6846)

Three packaging defects that made the documented à-la-carte path and the
bundle-size report unreliable:

- The README recipe imported only components + theme, but every component CSS
  file @applies shared utilities (status-focused, status-disabled, … — 176
  uses across 83 files) and custom variants (motion-reduce:). Document the
  required utilities + variants prelude so the recipe actually compiles.
- The CSS subpath exports (./components/*.css, ./utilities, ./variants,
  ./themes/default, ./base) only existed after clean-package rewrote the
  published package.json. Mirror them in the committed exports so monorepo
  consumers (docs, storybook) and the published surface match.
- measureBundleSizes() read dist/components/<name>/index.js, which under
  preserveModules is a ~500-byte re-export shim. Button was reported as
  0.21 KB gzip when its own modules total 0.72 KB, and the "total" summed
  overlapping scopes to ~24 KB against a real unique-file total of ~74 KB.
  Measure every .js under each component directory, report utils/ and hooks/
  separately, and compute total from the unique set of all dist .js files.
- Stop tracking the stale packages/styles/bundle-size.json (singular; 9 months
  out of date) and gitignore both filename variants.

* ci: speed up the pipeline and stop running dead work (#6845)

* ci: speed up the pipeline and stop running dead work

- Drop ^build from test / test:browser / test:coverage. Both @heroui/react and
  @heroui/styles resolve to source via their exports maps, so the forced styles
  rebuild bought nothing for the test job.
- Cache .turbo across QA jobs and cancel in-progress runs on the same ref.
  Merge the ESLint and TypeScript jobs into one install (Lint & Typecheck).
  continuous-release now needs: [build] so it only runs after a green build.
- Declare real outputs so Turbo can cache: typecheck writes *.tsbuildinfo;
  build also produces bundle-sizes.json and apps/docs/public/skills/**.
  Narrow lint/test/typecheck inputs to exclude stories/mdx. Mark start as
  persistent/uncacheable like dev.
- Delete update-stats.yml — it called update:search-meta and update:github-info,
  neither of which exists, and targeted base: main (not canary/v3*).
- Remove the six dead docs-cn scripts. Chinese docs already live under
  apps/docs/content/docs/cn; there is no @heroui/docs-cn package. Slim
  postinstall to just typegen:docs (drop the unconditional styles rebuild and
  the no-op typegen:docs-cn).
- Tighten pnpm-workspace globs so apps/docs/.contentlayer stops registering
  as a phantom "dot-contentlayer" package. Delete the redundant
  packages/testing/turbo.json.
- Unify declaration generation with the shared React tsconfig so shipped .d.ts
  and pnpm typecheck share the same strictness. Gate release.yml on
  lint/typecheck/test before publish. Drop the hardcoded git identity from the
  install action (only the deleted stats workflow needed it).

* ci: keep the styles build that browser tests need and fix type emit

Two regressions from the previous commit:

- Browser suites import packages/styles/dist/heroui.min.css by relative
  path, so dropping dependsOn: ^build from test / test:browser left that
  file ungenerated and every browser suite died behind a Vite error
  overlay. Restored on both; test:coverage stays without it since it is
  jsdom-only.
- tsconfig.build.json set jsx: react-jsx, which made the JSX-only React
  imports unused locals and tripped the noUnusedLocals inherited from the
  shared config. Inheriting jsx instead keeps emit identical to typecheck.

* fix(react): correct dependency declarations and production warning leak (#6847)

* fix(react): correct dependency declarations and production warning leak

- Promote @internationalized/date to peerDependencies. It is a real runtime
  import (utils/calendar.ts, calendar, range-calendar) and already marked
  external in rollup.config.mjs, but was only declared as a devDependency, so
  it resolved by accident through react-aria-components' own tree.
- Drop tailwind-merge from dependencies. Zero imports in src; it is already
  transitive via tailwind-variants, and pinning it directly was what produced
  the 3.4.0/3.6.0 duplicate in the lockfile.
- Drop @react-aria/i18n from peerDependencies and devDependencies. Zero
  references anywhere in the repo, but it forced an install on every consumer.
- Put `types` first and add a `default` fallback in every generated exports
  condition, so resolution is correct under moduleResolution node16/nextnode.
- Gate the ColorSlider channel/colorSpace warnings behind NODE_ENV.
  getValidColorSpace runs in the render body, so a misconfigured slider was
  warning on every render in production builds.
- Move tag's md padding and font-size into the base .tag block so bare
  `class="tag"` markup is not rendered with zero padding, per the documented
  default-size pattern.

* chore(docs): align tailwind-merge on 3.6.0

Dropping the unused direct tailwind-merge dependency from @heroui/react let
tailwind-variants resolve its optional peer freely, which floated the
library to 3.6.0 while apps/docs stayed pinned at 3.4.0 — two copies, and
class merging running on a version nobody picked. Align docs on 3.6.0 so
the workspace resolves a single copy.

Verified: full docs build from scratch, 614 jsdom + 42 browser + 27 docs
tests, typecheck, lint.

* ci(lint): make the ESLint job actually able to fail (#6851)

* ci(lint): make the ESLint job actually able to fail

eslint-plugin-only-warn is registered in the shared base config, so it applies
to every package and demotes every "error" severity to a warning. Combined with
a bare `eslint .` in each package's lint script, the QA ESLint job always exited
0 and could never block a PR. Only the pre-commit hook was enforcing anything,
because lint-staged already passes --max-warnings=0.

Add --max-warnings=0 to all three lint scripts so warnings become the gate,
keeping only-warn's editor ergonomics intact. Verified: a violation now reports
"ESLint found too many warnings (maximum: 0)" and exits non-zero.

The existing backlog was 2 auto-fixable import/order warnings in apps/docs,
both fixed here.

Also tighten two rules that were disabled more broadly than necessary:

- react-hooks/rules-of-hooks was off for all of @heroui/react. All 159
  violations are in .stories.tsx, where Storybook's lowercase render callbacks
  cannot be recognised as components; zero are in src/ or tests/. Scoped the
  disable to stories so real conditional-hook bugs are caught.
- react-hooks/exhaustive-deps was off globally, now "warn". Of the 4
  violations, the unnecessary itemIds.length dep in use-disclosure-group-
  navigation is simply removed, and calendar-year-picker's years and
  itemByYear are now memoized on the stable itemsKey instead of being rebuilt
  every render. That also fixes an effect that re-ran on every render because
  it depended on the unstable years array.

* fix(docs): pin the ESLint import resolver so lint is cwd-independent

Making lint blocking surfaced a pre-existing conflict: `pnpm lint` and the
lint-staged pre-commit hook demanded contradictory import order for the same
line, so no file content could satisfy both and each run reverted the other's
autofix.

Cause: the shared base sets `import/resolver: {typescript: true}`, which
discovers tsconfig from process.cwd(). Running eslint from apps/docs resolved
the `@/*` path aliases, but lint-staged runs it from the repo root where the
root tsconfig has no such paths, so import/order classified aliased imports
into a different group and produced the opposite ordering.

Pin the resolver to apps/docs/tsconfig.json. Both invocations now report
identical results, and the two import/order warnings are fixed.

* fix(calendar): keep year labels in sync with locale and format changes

The itemByYear memo added alongside the exhaustive-deps gate was keyed on
itemsKey, which encodes only id:year. item.formatted also depends on locale,
time zone and the format prop, so changing any of those left the grid
rendering year labels in the previous format. Key the memo on the labels too,
and wire @heroui/styles into turbo lint -- it had an ESLint config but no lint
script, so its 175 files were never checked.

* refactor(react): context memo (#6852)

* refactor(react): memoize context values and stabilize refs

54 of ~67 context providers were passing a fresh object literal every render
(`value={{slots}}`), so every consumer re-rendered whenever the root did —
even when slots itself was correctly memoized. React Compiler is not enabled
for the library build, so this was load-bearing and incomplete.

- Memoize 43 stable context values with useMemo, or hoist module-level
  constants for LabelContext ({elementType: "span"}) and SurfaceContext
  ({variant: "default"}). Highest-consumer wins: table (12), pagination (7),
  accordion (6), tabs/card/slider (5).
- Leave the 13 providers that feed live RAC render-prop state untouched;
  those need a slots/state context split, which is out of scope here.
- Memoize mergeRefs(...) in date-picker, date-range-picker, autocomplete (x2),
  and scroll-shadow. Passing a fresh mergeRefs result as `ref` was detaching
  and reattaching the DOM node on every render.
- Replace `= []` defaults that defeated their own useMemo deps
  (input-otp validationErrors, disclosure-group itemIds, combo-box siblings)
  with module-level EMPTY_* constants.
- Hoist currentYear into the calendar-year-picker grid context so each cell
  no longer allocates a Date (default range is 1900–2099 ≈ 200 cells).

* refactor(calendar): read currentYear per render instead of memoizing on []

Hoisting currentYear out of the cells is the win -- a grid was allocating one
Date per year instead of one total. Memoizing it on [] also froze it for the
component's lifetime, so a calendar mounted across midnight on Dec 31 would keep
highlighting the old year. It's a number, so the context memo still compares it
by value and the stability benefit is unchanged.

* refactor: remove dead code, dead CSS, and outdated agent guidance (#6853)

* chore: remove dead code, dead CSS, and outdated agent guidance

- Drop unused createVariantBuilder / createVariants (~150 lines) from
  utils/variants.ts; only mapPropsVariants remains. Drop unused
  getValidChildren from utils/children.ts.
- Delete the abandoned separator compound subsystem (7 classes never
  referenced by separator.styles.ts), plus .checkbox--disabled,
  .radio--disabled, and .switch__label (migration residue — Switch
  composes the shared Label).
- Stop emitting phantom drawer__dialog--{left,right,bottom} classes;
  placement styling already routes through drawer__content--*, and only
  --top has a real CSS rule (handle padding).
- Rename fieldset__field_group → fieldset__field-group (BEM).
- Docs: remove the .radio--disabled listing that documented a class that
  never existed in the styles map. AGENTS.md: replace forwardRef examples
  with React 19 ref-as-prop, and show a memoized context value.

* chore: keep drawer dialog hooks, sync fieldset docs and CLAUDE.md

Three follow-ups to the dead-code pass:

- Restore the drawer placement dialog mappings. drawer__dialog--bottom/left/right
  carry no CSS, but they are applied to the DOM and documented as public styling
  hooks in both locales, so dropping them would silently kill consumer overrides.
  Empty modifier hooks are an established convention here (see .avatar--md).
- Rename fieldset__field_group to field-group in the en and cn docs. The class was
  renamed in CSS and styles.ts but the docs still showed the old name in a CSS
  override example and a class listing.
- Apply the same React 19 ref-as-prop template to CLAUDE.md that AGENTS.md got.
  Both are always-applied agent rules, so updating only one left them
  contradicting each other -- CLAUDE.md still said to use forwardRef, which no
  component in src/components does.

* fix(styles): remove stacked breadcrumbs padding so crumbs sit on-grid (#6854)

* fix(styles): remove stacked breadcrumbs padding so crumbs sit on-grid

* fix(styles): restore breadcrumbs spacing with gap instead of padding

* fix: address v3.2.6 review follow-ups (#6855)

* docs(agents): correct stale postinstall gotcha

* fix(react): return undefined from pickChildren when no targets match

* test(autocomplete): compile browser styles via @/styles.css

* test(scroll-shadow): compile browser styles via @/styles.css

* test(tag): compile browser styles via @/styles.css

* refactor(styles): remove empty input focus-visible rules

* refactor(styles): remove empty number-field hover and focus rules

* refactor(styles): remove empty textarea focus-visible rules

* docs(releases): add v3.2.6 release notes for en and cn

* feat(component): avatar group (#6861)

* feat(component): avatar group

* docs(component): avatar group

* fix(docs): remove stale avatar group demo export

* chore(docs): update release notes

* chore(docs): update release notes

* build(deps): bump patch and minor dependencies across workspace (#6866)

* build(deps): bump patch and minor dependencies across workspace

Bump 89 dependencies across all six workspace manifests, restricted to
patch and minor releases. All 27 available majors are skipped (eslint 10,
vitest 5, typescript 7, vite 8, @babel/core 8, motion 13, shiki 4,
lucide-react 1.x, @vercel/og 1.x and others).

Also align intra-repo version drift: tailwindcss was 4.1.18 in
packages/react but 4.3.0 elsewhere, typescript was 5.8.3 in
packages/styles, @storybook/react was 10.1.10 in packages/react,
@vitejs/plugin-react differed between two packages, and
babel-plugin-react-compiler was on a stale 19.1.0-rc.3 at root while
apps/docs already used the stable 1.0.0.

Pin typescript in packages/react, packages/storybook and apps/docs, and
jsdom in packages/react. These packages run tsc and vitest without
declaring them, so pnpm auto-installed the optional peers at latest on
lockfile refresh: typescript floated to 7.0.2 (breaking the build on
baseUrl removal) and jsdom floated to 30.1.0, overriding the pin in
@heroui/testing.

Hold back three minors that ship breaking changes:

- fumadocs-core/fumadocs-ui stay at 16.9.0. 16.10.0 removed the
  I18nLabel export and dropped `text` from LocaleContextType, both used
  by the docs app.
- fumadocs-typescript stays at 5.0.0. 5.4.1 moves typescript from a peer
  to a hard ~7.0.2 dependency and adds shiki ^4.
- eslint-plugin-react-hooks stays at 7.0.1. 7.1.1 changes which sites
  its rules fire on and flags setState-in-effect calls in the
  in-progress calendar-year-picker.

jsdom is held at 30.0.1: 30.1.0 dispatches focusout with a non-Node
relatedTarget, which throws inside react-aria's isBlurFromActiveElement
and broke the Select suite.

* style: reformat for prettier 3.9 and tailwind plugin 0.8

Formatting fallout from the prettier 3.7.4 -> 3.9.7 and
prettier-plugin-tailwindcss 0.7.2 -> 0.8.1 bumps. Without it
`pnpm lint` and `pnpm format` fail.

Four files pick up prettier 3.9's union type collapsing, where a union
that fits on one line no longer gets leading pipes. table.stories.tsx
picks up a class reorder from the tailwind plugin, moving `scrollbar`
ahead of `overflow-auto`; the two do not conflict, so rendering is
unchanged.

No behavioral changes.

* chore(docs): update release note

---------

Co-authored-by: WK <wingkwong.code@gmail.com>
Co-authored-by: Tianen Pang <32772271+tianenpang@users.noreply.github.com>
2026-09-17 12:03:26 -07:00
..
2026-09-17 12:03:26 -07:00
2026-05-23 11:39:32 +08:00
2026-09-17 12:03:26 -07:00
2026-09-17 12:03:26 -07:00
2026-08-13 15:32:36 -07:00
2026-09-17 12:03:26 -07:00
2026-09-17 12:03:26 -07:00
2026-07-31 06:39:59 -07:00