Repository navigation
v0.4.0
Minor Changes
-
5cebea1: New:
ChatBubble.Typing(also exported asChatBubbleTyping). The "someone
is typing" indicator: three pulsing dots behind arole="status"with a visually
hiddenlabel(default"Typing"), so consumers no longer hand-build it. The
pulse is the sharedanimate-fuji-pulseand stops under
prefers-reduced-motion: reduce. Pure CSS, soChatBubblestays
server-renderable.New:
ChatBubble.Attachmenttakes apreview. Artwork - an image thumbnail,
a video poster - rendered in place of the icon, cropped into a small rounded
square (resizable through the newclassNames.preview). Image attachments had no
way to show what they were. WithoutonClickthe chip is still a plain<div>,
so it can sit inside a consumer's own link.New:
BottomNavigationitembadgeandbadgeLabel. A count pill pinned to
the item's icon, hidden at0and capped at99+. A numeric badge is announced
as part of the item's name ("Chats, 3 unread") through visually hidden text;
badgeLabelchanges that wording, or names a non-numeric badge. Previously an
unread count had to be drawn into the icon by hand, and was never announced.Fixed:
renderLinkdroppedaria-current.Navbar,BottomNavigationand
Breadcrumbhanded a customrenderLinkonly the item's content, so a router
link lost what the default anchor carried:aria-current="page"on the active
item - the only non-colour signal of it - plus the link reset and focus ring.
renderLinknow receives a third argument with exactly those props (href,
aria-current,className,children, and onBottomNavigationtheonClick
that reportsonItemSelect), exported asNavbarLinkProps,
BottomNavigationLinkPropsandBreadcrumbLinkProps. They are also applied to
the elementrenderLinkreturns, filling in only what it does not set itself,
so existing(item, children) => <Link href={item.href}>{children}</Link>
callbacks are fixed without changes. One visible effect: aBottomNavigation
router link now firesonItemSelect, as its default anchor always did.Fixed:
Statisticand the charts could fail hydration. Numbers were
formatted with the runtime's default locale, so a server and a browser in
different locales rendered different digits ("1,234" vs "1.234"). They now
format with a fixed"en-US"locale by default, and take alocaleprop to
localize explicitly.Statisticalso takesformatValuefor currency, compact
notation or units. This changes the default output for anyone whose runtime
locale was not English: passlocaleto keep localized figures.Fixed: line and bar charts had a 380px minimum width. The plot carried
min-w-[380px], so in a card on a 320-375px screen it overran the card and
could only be scrolled sideways. It now shrinks with its container like any
scaled image. At very narrow widths the axis text scales down with it; the
sr-onlydata table still carries the exact values.Fixed:
Carouselcoverflow ignoredslidesPerView. It now sets the centre
slide's width (100% / slidesPerView, clamped to at least 1), including the
responsive map form; without it coverflow keeps its 1.6 default. A non-looping
coverflow also now lets every slide become active - it computed its last index
as if it were the flat preset, which stopped short of the final slide.New:
TreeexpandOnSelect(defaulttrue, unchanged behaviour). Selecting
a parent both selected and toggled it, so a folder could not be selected without
opening or closing it.expandOnSelect={false}decouples them per the WAI-ARIA
tree pattern: a row click and Enter/Space only select, the chevron toggles on
click, and ArrowRight/ArrowLeft expand and collapse.Fixed: grouped
Timelinetimestamps were cut to 32px. The timestamp column
was a fixedw-8, which fit a two-digit year and nothing else - "Sep 14" or
"9:41 AM" overflowed into the title. Timestamps now share one column sized to
the widest in the group (min-w-8as the floor, so short stamps align as
before), and titles stay aligned across rows, including rows with no timestamp.Fixed:
FloatingActionBarfought consumer positioning. Its root carried
relative, so whetherclassName="fixed bottom-6 right-6"won depended on
the page's cascade-layer order. The root is now unpositioned and the column anchors to an
inner wrapper. Actions are no longer keyed bylabelalone - two actions with
the same label caused React key warnings - and take an optionalidto key by.New:
Cardpadding("none" | "sm" | "md" | "lg", default"md"- the
existing 20px). A consumerp-0for flush content only wins with the right
cascade-layer order, and leftCard.Mediacancelling 20px of padding that was
no longer there. The prop works regardless of stylesheet order, and
Card.Mediafollows it, so media still bleeds to the edge at any value. New type:CardPadding. -
5cebea1: Date and form-field fixes found while building example apps, plus two small
Calendaradditions.DatePickerandTimePickerignored theirFormField. Their triggers were
barePopover.Triggers, invisible to Base UI's Field:FormField.Label's
forpointed at an id nothing rendered (clicking it did nothing, and the
trigger had no accessible name),FormField.Descriptionwas never announced,
and<FormField invalid>painted neither the red border noraria-invalid.
Both triggers now render throughField.Control- the same Field-aware leaf
Inputuses - so they get the field'sid,aria-labelledby,
aria-describedby,data-invalid/aria-invalidanddisabledlike every
other Fuji field. A FormFieldvalidatefunction receives the selected day as
YYYY-MM-DD(DatePicker) or theHH:mmstring (TimePicker).TimePickeralso
gains thearia-labelledbyprop its siblings already had.Combobox,MultiSelect, andNumberInputerased their FormField label
reference. Each passedaria-labelledby={undefined}explicitly, which wins
Base UI's merge over the label id it sets - the same stomping pattern fixed for
data-invalidearlier. The fields stayed named only throughlabel[for]; they
now keeparia-labelledbypointing at the label too.minDate={new Date()}disabled today.Calendar(and soDatePicker)
compared bounds by timestamp, so today's midnight cell sat "before" aminDate
carrying the current time.minDate/maxDatenow compare by calendar day,
including when clamping the keyboard focus position.MultiSelect'ssizeonly changed its font size. The field hard-coded the
mdcontrol height, sosmandlgrendered as tall asmdbeside a
SelectorInputof the same size. Height, input row, and chip padding now
followsize.Slider'saria-labelnever reached the slider. It was spread onto the
wrapping group, leaving the focusablerole="slider"input unnamed. It is now
applied to the thumb input. A range value ([20, 80]) also rendered only one
thumb - the second value had no handle at all;Slidernow renders one thumb
per value, and the newgetAriaLabel(index)prop names each one
("Minimum price" / "Maximum price").New:
CalendarmarkedDatesandmarkedDateLabel. Pass a list of dates
(matched by day) or a predicate to put a small dot under days with something on
them. The mark is not visual-only:markedDateLabel(default"marked") is
appended to each marked day's accessible name, e.g. "Monday, May 20, 2024, has
tasks".New:
todayonCalendarandDatePicker. "Today" always came from the
real clock (reconciled after mount), so demos and tests could not pin it.
todaysets the date used for the highlight,aria-current="date", and the
initial month; it renders identically on server and client. Omit it to keep
the existing behavior. -
5cebea1: Overlay, palette, keyboard and field fixes found while building example apps
against the package, plus the small APIs those apps had to work around.Behaviour changes, under a minor bump (pre-1.0). SPEC.md §10 asks for the
callout, so: the standaloneKeyboard layout="numpad"no longer has a
NumLockcap (it is nowBackspace); an in-flowKeyboardno longer takes
focus when a cap is clicked; an explicitKeyboard widthis no longer capped
by the size's cap ceiling; aTextareagivenrowsno longer has the size's
minimum height; andCommandMenulists ungrouped items first even when a
grouped item comes earlier initems. Each is below, with why.Keyboardnever steals focus. Only afloatingboard swallowed the
mousedown that moves focus onto a clicked cap, so an inline keypad beside an
input blurred that input on every press and consumers wrapped it in a div
cancelling mousedown themselves. Every interactive board does it now. Caps are
still buttons reached with Tab and the arrow keys.Keyboardfills its container when asked. An explicitwidthwas still
capped by the size's cap ceiling (38px caps atmd), so in flow
width="900px"drew a 630px board and a numpad could never fill its card; an
explicit width now lifts that ceiling. A percentage was read against the grid
itself rather than the space it was given -width="100%"collapsed a numpad
to an 88px sliver - and is now a share of the box the board sits in, so
width="100%"spans its parent. The board still never outgrows that box. The
container-bound cap unit also forgot the deck's 1px border, leaving the
right-hand inset 2px thinner than the left; measured symmetric at 320px and
375px, with no horizontal overflow at either.Keyboard's numpad can delete. A PIN or OTP keypad built on
layout="numpad"had no way to remove a digit. Its top-left cap is now a⌫
Backspace (codeBackspace, the same glyph and name as thephoneboard's) in
place of Num Lock, which on a drawn board typed nothing and toggled nothing.
fullkeeps its Num Lock.Popover.Contenttakesside,alignandalignOffset. Everything but
sideOffsetwas spread onto the popup rather than Base UI's Positioner, where
it was silently dropped.Tooltip.Contenthad the same gap and gains the same
three props;DropdownMenu.Content, which already hadalign, gainsside
andalignOffset. Defaults are Base UI's own.CommandMenucan open a second step and bind ⌘K. An item with
closeOnSelect: falsekeeps the palette open after itsonSelect, clears the
query and returns focus to the search field, so swappingitemsshows the next
step. Newhotkeyprop:hotkey="k"toggles the palette on ⌘K or Ctrl+K
through the same controlled/uncontrolled state as everything else, with the
listener removed on unmount. Off by default. The arrow keys also walked
itemsin array order while rows are drawn bucketed by group, so with groups
interleaved the highlight jumped around the list; navigation, Enter and
aria-activedescendantnow follow the drawn order.DialogandDrawerscroll. Both capped the popup at the viewport and
then let taller content spill past it, with the page scroll-locked behind so
the end was unreachable. The popup now scrolls its own content without
chaining into the page.Drawer.Contentgainswidth(sm16rem |md20rem |lg28rem |
full, defaultmd- the old fixed width) forleft/rightpanels, still
capped atcalc(100vw - 3rem). Namedwidthrather thansizefor the reason
Container's is:sizeis a control's height/padding scale everywhere else.
New typeDrawerWidth.InputandSearchInputkeep their height in a flex column. In an
overflowing flex column - a Sidebar - the field root's automatic minimum
height is one line of text, and it collapsed from 38px to a 19px sliver. The
root now carries amin-heightequal to its control height. Notshrink-0:
every field isw-full, and in a flex row (a search box beside a button)
shrink-0measured the button pushed 83px out of its container.Textarea
was measured unaffected.Textarea rowssets the height. The size's minimum height (5rem / 6rem /
8rem) beat the heightrowsasked for, sorows={1}still drew a 96px box.
Withrowsgiven, that minimum is dropped - a one-line composer that grows
with its content is now possible.
Patch Changes
-
5cebea1: Fixed: a utility passed through
classNamecould not override a component's
own styling in a Tailwind v4 app.<Card className="p-0">,
<Sidebar className="w-full">,<Drawer className="w-[28rem]">and margins
on<Typography>all silently did nothing.A cascade layer ranks by the first time its name appears on a page, and that
ranking is compared before specificity.styles.cssonly declared its own
fuji.*layers, so where they landed depended on which stylesheet the bundler
emitted first. If the app's Tailwind CSS came first,fujiranked above the
app'sutilitiesand every override lost. Ifstyles.csscame first,fuji
ranked below the app'sbase, and Tailwind's preflight
(* { padding: 0; margin: 0; border: 0 solid },
button { background-color: transparent }) stripped the padding, borders and
fills off every component.styles.cssnow opens with
@layer properties, theme, base, fuji, components, utilities;, which puts
Fuji above the app's preflight and below its own components and utilities
whenever the package stylesheet loads first. To get that order whichever file
loads first, add the same line at the top of your global CSS, above every
@import:@layer properties, theme, base, fuji, components, utilities;
Fuji's rules still live only in its own
fuji.*layers. The bare layer names
are only declared, and stay empty. Apps whose CSS is not in any layer (plain
CSS, Tailwind v3) are unaffected: unlayered CSS already outranks every layer.
See "Overriding a component's styles withclassName" indocs/theming.md.Checked in headless Chrome against a real Tailwind v4.1 build. Without the
line, loading the app's CSS first still loses overrides and loading
styles.cssfirst now works. With the line, both orders pass, and so does
importing both files from oneglobals.css. Checked for both orders:
overrides for padding, width, background, margin and display win; Fuji's
padding, border, fill, margin and font weight survive preflight; and
fuji.componentsstill beatsfuji.utilities.