Releases: code-sherpas/pharos-react
Release list
v0.14.3
Patch Changes
-
d67e70c: fix(Combobox): anchor the multi-select popup to the chips control box, not the input
In a
Combobox multiple, Base UI resolves the popup anchor as
inputGroupElement ?? inputElement. Single-select wraps Base UI'sInputGroup
(ComboboxControl), so the popup anchored to the full-width control. But
multi-select usesCombobox.Chips(ComboboxChips) as the bordered box, and
Chipsregisters noinputGroupElement— so the anchor fell back to the
Input, which floats at the end of the chip row. Once several chips pushed the
input toward the right edge, the popup opened narrow and shifted right
(anchored at the typing caret) instead of keeping the control width.ComboboxChipsnow publishes its DOM node through an internal context that
ComboboxContentreads and passes as thePositioner'sanchor, so the popup
keeps a constant width = the control (width: var(--anchor-width)),
left-aligned, regardless of where the input sits. Single-select is unchanged:
no chips box means no anchor override, so Base UI keeps anchoring to the
ComboboxControlas before.No public TypeScript API change. Surfaced in Alexandria's
AddUsers/
AddSkillsmulti-select pickers.
v0.14.2
Patch Changes
-
d70eb1a: fix(overlays): give the anchored popups a configurable z-index so they clear host-app overlays
Combobox,Select,DropdownMenuandPopoverportal their popup to the end
of<body>, which floats it above in-flow content — but not above a
host-app overlay that carries its ownz-index(a drawer, a modal). Worse, a
z-indexset on the popup surface (*Content) is inert: Base UI'sPositioner
anchors withtransform, which creates a stacking context that traps any
z-indexon its descendants. So a consumer could not lift the popup from the
outside at all.The
z-indexnow lives on the Positioner (where it is not trapped), sourced
fromvar(--pharos-z-index-popover, 1000). Apps with no high z-index scale get
a sensible default; an app whose own overlays sit higher (e.g. a drawer at
z-index: 10000) raises every Pharos popup at once by overriding
--pharos-z-index-popoverin:root— no per-call-site className. This closes
the long-standing "shared--pharos-z-index-*scale" follow-up the four
overlay modules carried.No public TypeScript API change. Surfaced while adopting the
Combobox-multiple
pickers in Alexandria, whoseAddSkillspicker lives inside az-index: 9999
drawer and had its listbox rendering behind the drawer.
v0.14.1
Patch Changes
-
a9605d8: fix(Select): anchor the listbox below the trigger instead of overlapping it
SelectContentnow forces Base UI'salignItemWithTriggertofalse. Base UI's
Select.Positionerdefaults it totrue— the native-<select>/ macOS
behaviour that floats the popup over the trigger and, as a side effect,
silently ignores theside/align/sideOffsetprops (it forces
renderedSide = 'none'). The listbox therefore covered the trigger box, and the
documented "opens below the trigger, 8px away" defaults never applied.With
alignItemWithTrigger={false}the listbox opens below the trigger with
the 8pxsideOffset, matching the dominant web convention (shadcn
position="popper", Ant, Mantine) and — decisively — makingSelectconsistent
withCombobox, whose Base UI primitive has no item-alignment mode and always
anchors adjacent. No public API change. D17 refinement; see NAMING-decisions § Select + Combobox.Also fixes a pre-existing critical a11y issue surfaced by the same change:
SelectSeparatorre-exported Base UI's genericSeparator(role="separator"),
which is not a permitted child of thelistboxrole and failed axe's
aria-required-childrenon a grouped Select. It now renders decoratively
(aria-hidden,role="none") — the grouping semantics already live on
SelectGroup/SelectLabel, matching Radix / shadcn.
v0.14.0
Minor Changes
-
6345498: Add the
SelectandComboboxatoms — the selection family (Decision D17).Two atoms split by interaction contract, matching the shadcn / Base UI / ARIA
APG consensus:Select— pick from a known set of options (the ARIA listbox pattern,
no text input). Wraps Base UI'sSelect.*. Parts:Select,SelectTrigger,
SelectValue,SelectContent,SelectItem,SelectGroup,SelectLabel,
SelectSeparator.Combobox— a text input that filters a known set (the APG combobox
pattern). Wraps Base UI'sCombobox.*. Parts:Combobox,ComboboxControl,
ComboboxChips,ComboboxInput,ComboboxTrigger,ComboboxClear,
ComboboxChip,ComboboxChipRemove,ComboboxContent,ComboboxList,
ComboboxItem,ComboboxEmpty,ComboboxGroup,ComboboxGroupLabel,
ComboboxSeparator.
Multi-select is the
multipleaxis on each root (not a separate atom);
single-select closes on pick, multi-select keeps the popup open. Both popups
takemin-width: var(--anchor-width)so they are never narrower than their
control. The trigger/control share the Input chrome (WCAG 1.4.11 border tone,
brand focus ring,sm/md/lgsize grid). Following Escuela 1 (D11) neither
atom owns label/helper/error — compose those and convey error via
aria-invalid.
v0.13.0
Minor Changes
-
0cb3869: Add the
Popovercompound atom (Decision D16).Free-form content anchored to a trigger, wrapping Base UI's
Popover.*
parts, which implement the ARIA dialog pattern as a non-modal
disclosure:role="dialog"on the popup, focus moves into it on open and
returns to the trigger on close, Escape and outside-click dismiss it, and
focus is NOT trapped (the rest of the page stays interactive).Naming follows shadcn rather than Base UI's
Popoverparts. The surface
collapses Base UI'sPortal+Positioner+Popupinto a single
PopoverContentexposingside/align/sideOffset/alignOffset
(aligndefaults tocenter, unlike DropdownMenu'sstart). Parts shipped
in v1:Popover,PopoverTrigger,PopoverContent,PopoverTitle,
PopoverDescription,PopoverClose.Deliberately distinct from the
DropdownMenuatom (D15): a DropdownMenu is
for commands (menu-button contract —role="menu"/menuitem, roving
focus); a Popover holds arbitrary content (forms, navigation, panels) under
the disclosure/dialog contract. The two wrap different Base UI primitives
(MenuvsPopover) and share no machinery — 7 of 8 surveyed top-tier
design systems separate them.PopoverArrow, a non-triggerPopoverAnchor
(the future Combobox will want it) and a modalBackdropare deferred — no
consumer exercises them yet and the compound API makes them additive without
a breaking change.
v0.12.0
Minor Changes
-
270aec0: Add the
DropdownMenucompound atom (Decision D15).An action menu anchored to a trigger, wrapping Base UI's
Menu.*parts,
which implement the ARIA APG menu-button pattern (role="menu"/
menuitem, roving focus, arrow-key navigation, typeahead, and
Escape-to-close with focus return to the trigger).Naming follows shadcn rather than Base UI's
Menu. The surface collapses
Base UI'sPortal+Positioner+Popupinto a single
DropdownMenuContentexposingside/align/sideOffset. Parts
shipped in v1:DropdownMenu,DropdownMenuTrigger,
DropdownMenuContent,DropdownMenuItem(with adestructivevariant),
DropdownMenuSeparator,DropdownMenuLabel,DropdownMenuGroup.Deliberately distinct from a future
Popoveratom: a DropdownMenu is for
commands (menu-button contract); free-form anchored content belongs in a
Popover (disclosure/dialog contract). 7 of 8 surveyed top-tier design
systems separate the two.CheckboxItem,RadioItem, submenus and
LinkItemare deferred — no consumer exercises them yet and the compound
API makes them additive without a breaking change.
v0.11.1
Patch Changes
-
f052ca8: Mark the published ESM bundle as client-only via a
"use client"banner.Avatar (D14) calls
createContext(...)three times at module top level
(shape / loading / group). Without the banner, Next.js evaluates the
Pharos module during its RSC build-time analysis pass and fails with
TypeError: (0 , c.createContext) is not a functionwhile collecting
page data for any route whose layout transitively imports an Avatar.The banner is the same convention every React DS ships with
(MUI / Chakra / Radix / shadcn). The atoms that have no hooks
(Card / Separator) lose server-render-ability as a side effect; this is
acceptable since they were never advertised as server-safe and React DS
libraries are universally client-only today.The Avatar source file also carries its own
"use client"directive so
the intent is visible to anyone reading the source.
v0.11.0
Minor Changes
- 584821e: Add the
Avatarcompound atom (Decision D14). ShipsAvatar,
AvatarImage,AvatarFallback, andAvatarGroupas named exports.- Three named sizes (
sm/md/lg= 32 / 40 / 48 px) align with
the IconButton height grid; a numericsize={number}writes
width/heightinline for one-off cases (profile pictures,
compact stacks). - Two shapes (
circledefault,squarevia theradius-mdtoken). - Compound API (
AvatarImage+AvatarFallback) follows the
shadcn / Radix / Base UI / Chakra contract — the fallback content is
composed explicitly by the consumer (Escuela 1, D11). No magic
nameprop, no built-in initials computation. AvatarGroupstacks Avatar children with proportional negative
margin and abox-shadowring that defaults to the page surface
(--pharos-avatar-group-ring). Themaxprop caps visible avatars
and collapses the surplus into a final+NAvatar with accessible
name{N} more.renderprop on the root composes the Avatar as a different element
while keeping its styles (e.g.render={<a href="/profile" />}).- No status-badge primitive in v1 — deferred to composition with a
future Badge positioning helper.
- Three named sizes (
v0.10.0
Minor Changes
-
a3f5ef3: Add
IconButtonatom — icon-only pressable control. Dedicated atom rather than a<Button size="icon">variant (Decision D13, 2026-04-30): six of eight top-tier DSes ship a dedicatedIconButton(Material 3, MUI, Chakra, Radix Themes, IBM Carbon, plus Mantine'sActionIcon), and a dedicated atom enforcesaria-label(oraria-labelledby) at the type level instead of leaving WCAG 4.1.2 compliance as a runtime warning the way Chakra / Mantine do.Public API mirrors Button:
intent="primary | secondary | ghost | destructive"×size="sm | md | lg"× native<button>props × Base UIrenderprop for composition (<IconButton render={<Link to="/next" />} aria-label="Next">). Defaults:intent="ghost"(the dominant icon-only case is low-emphasis — close, dismiss, toolbar action),size="md". Square dimensions matching the Button height grid (32 / 40 / 48 px);border-radius: var(--pharos-radius-full)produces a perfect circle. Direct-child> svgselector sizes Lucide icons (which ship with hardcodedwidth="24" height="24") to 16 / 20 / 24 px to match the icon slot.isLoadingprop wired into the atom because the icon-only case is materially worse without it: with a Button the consumer can compose<Button disabled><Spinner /><span>Saving</span></Button>, but with an IconButton the slot has to be swapped (icon while idle, Spinner while loading), forcing two render branches at every async call-site. SettingisLoadingswaps the slot for<Spinner size={size}/>, setsdisabled, and exposesaria-busy="true".No integrated Tooltip (diverges from IBM Carbon's mandatory tooltip):
aria-labelalready satisfies WCAG 4.1.2; tooltip is a separate composition concern and a separate atom (not yet shipped). Noselectedtoggle state in v1: no Alexandria call-site exercises the toggle pattern today; additive when needed.Adoption contract documented in
NAMING-decisions.md— the release pairs with an Alexandria PR that replacesCloseButton,CloseButtonCircle,NextCircleButton,PreviousCircleButtonwith the corresponding<IconButton>mappings, and folds the icon-only call-sites ofAsyncLoadingButton/PublishButton/UnpublishButton/AddButtoninto the atom's built-inisLoading.
v0.9.0
Minor Changes
-
92d84b0: Add
Spinneratom — visual loading indicator withrole="status"for assistive tech, three sizes (sm/md/lg) aligned with the form-control grid (16 / 20 / 24 px), andcurrentColorinheritance so it picks up the parent's text colour automatically (composition cases like<Button intent="primary"><Spinner /></Button>work out of the box, noButton isLoadingprop required at the atom level).Inline SVG with CSS
@keyframesrotation — zero icon-library dependencies (nolucide-react, noframer-motion). Honoursprefers-reduced-motionby slowing the rotation to 4 s instead of removing it (assistive UX still benefits from the in-progress cue).srLabelprop (default"Loading…") is rendered as visually-hidden text inside the status node; pass an action-specific label ("Saving template…","Deleting…") when relevant. Notoneaxis on the atom; consumers set the colour by settingcoloron the parent or viaclassName— same pattern Radix Themes / shadcn / Mantine use.