Summary
On initial mount, the Tabs indicator (both segmented pill and underline variants) appears instantly at full opacity. The intended pop-in — scale-90 → scale-100 + opacity-0 → opacity-1 over 200ms — never runs.
Root cause
The indicator in packages/kumo/src/components/tabs/tabs.tsx is styled with:
data-[rendered=false]:scale-90 data-[rendered=false]:opacity-0
but Base UI's TabsIndicator (v1.6.0) never sets a data-rendered attribute, so these rules are dead CSS — the element is painted at full opacity/scale from its first frame.
For the record, Base UI does correctly avoid the 0×0 flash: it measures the active tab during render and keeps the element hidden until isTabSelected && width > 0 && height > 0. Only the pop animation is missing.
Expected
Indicator pops in at the correct size/position (scale 0.9 → 1, opacity 0 → 1, ~200ms), as the dead classes suggest was intended — never animating its size from 0, never appearing instantly.
Suggested approaches
- Drive
data-rendered from a small piece of state: start false, flip to true on a later frame after the first measurement (e.g. double requestAnimationFrame from a mount effect), guaranteeing one painted frame at the pre-pop state so the CSS transition actually runs.
- Or drop the dead classes and use
@starting-style + transition-behavior: allow-discrete (modern browsers only).
Environment
@base-ui/react ^1.6.0
main @ 65db2e6
Summary
On initial mount, the Tabs indicator (both segmented pill and underline variants) appears instantly at full opacity. The intended pop-in —
scale-90 → scale-100+opacity-0 → opacity-1over 200ms — never runs.Root cause
The indicator in
packages/kumo/src/components/tabs/tabs.tsxis styled with:but Base UI's
TabsIndicator(v1.6.0) never sets adata-renderedattribute, so these rules are dead CSS — the element is painted at full opacity/scale from its first frame.For the record, Base UI does correctly avoid the 0×0 flash: it measures the active tab during render and keeps the element
hiddenuntilisTabSelected && width > 0 && height > 0. Only the pop animation is missing.Expected
Indicator pops in at the correct size/position (scale 0.9 → 1, opacity 0 → 1, ~200ms), as the dead classes suggest was intended — never animating its size from 0, never appearing instantly.
Suggested approaches
data-renderedfrom a small piece of state: startfalse, flip totrueon a later frame after the first measurement (e.g. doublerequestAnimationFramefrom a mount effect), guaranteeing one painted frame at the pre-pop state so the CSS transition actually runs.@starting-style+transition-behavior: allow-discrete(modern browsers only).Environment
@base-ui/react^1.6.0main@ 65db2e6