[Feature] Bubble: match shadcn (Base UI + vega)
Merged
CI
3d ago+214+214+012345678901234567890123456789-5-5-0123456789
Restyles Bubble to match shadcn's Base UI + vega Bubble. After this PR the shadcn audit finds no Look, HTML, Props or ARIA differences.




Changes
- BubbleContent uses
rounded-xl py-2, as in vega: 14px corners and 8px vertical padding. It usedrounded-3xl py-2.5(24px and 10px), so bubbles looked like pills and were 4px taller. - When BubbleContent renders as a button or link, its keyboard focus ring is
ring-ring/50, as in vega. It wasring-ring/30, which was harder to see. - Bubble gets vega's
group-data-[align=end]/message:self-end, so a Bubble insideMessage(align: :end)moves to the end on its own. In the usualMessage > MessageContent > Bubblenothing changes, because MessageContent already does this. It shows when the Bubble sits in another wrapper, like the "Aligned by the bubble itself." case in the screenshots, which stayed at the start onmain. - Bubble is now part of the shadcn audit (
audit/component_map/bubble.yml,audit/fixtures/bubble/). The fixture covers all seven variants, reactions on both sides, a button bubble, and two bubbles inside an end-aligned Message (one in MessageContent, one in a plain column).
Possible breaking change for apps that copied the component: bubbles get squarer corners and are 4px shorter. To keep the old look, pass class: "rounded-3xl py-2.5" to BubbleContent.
Test steps
cd gem && bundle exec rakecd docs && bin/dev, open/docs/bubbleand/docs/message: check the variants, alignment and reactions in light and dark.- On
/docs/bubble, press Tab until a button bubble has focus: the ring should be clearly visible.
shadcn audit, main
shadcn audit ruby-ui/ruby_ui
base 27edd81c head 27edd81c shadcn c0d63c33
⚠️ Environment font: "Inter Variable", "system-ui", -apple-system, "system-ui", "Segoe UI", Roboto, "Helvetica Neue", Arial, "sans-serif" vs "Inter Variable", sans-serif (light)
⚠️ Environment font: "Inter Variable", "system-ui", -apple-system, "system-ui", "Segoe UI", Roboto, "Helvetica Neue", Arial, "sans-serif" vs "Inter Variable", sans-serif (dark)
⚠️ Environment RubyUI preview boot: the global Ruby 3.4.7 on macmini has rdoc 8.1.0, whose RubyGems plugin activates date 3.6.0 before Bundler; the throwaway preview lockfile was bumped from date 3.5.1 to 3.6.0 to boot. No effect on rendering.
✅ Theme neutral tokens match shadcn
Bubble (ref: bases/base/ui/bubble.tsx + style-vega.css @ c0d63c33)
❌ Look border-bottom-left-radius: 24px; border-bottom-right-radius: 24px; border-top-left-radius: 24px; border-top-right-radius: 24px; padding-bottom: 10px; padding-top: 10px
vega: border-bottom-left-radius: 14px; border-bottom-right-radius: 14px; border-top-left-radius: 14px; border-top-right-radius: 14px; padding-bottom: 8px; padding-top: 8px
BubbleContent — closed/light, closed/dark. RubyUI rounded-3xl py-2.5 vs vega rounded-xl py-2 (cn-bubble-content). rounded-xl = calc(var(--radius) * 1.4) = 14px with --radius 0.625rem. Requested: use rounded-xl py-2 on BubbleContent.
↳ style-vega.css:1464
⏸ Look properties: font-family, height
BubbleGroup, Bubble, BubbleContent, BubbleReactions — explained by environment diff: font. Both sides resolve to Inter Variable first; only the fallback stack differs. Heights also differ by the py-2.5 vs py-2 BubbleContent padding above. Kept as is: environment (fallback font stack); height follows the BubbleContent padding fix.
✅ ARIA BubbleGroup, Bubble, BubbleContent, BubbleReactions: accessibility tree matches
↳ accessibility tree (shadcn-audit collect)
❌ Look BubbleContent (as: :button) focus-visible: ring-3 ring-ring/30 → box-shadow oklab(0.708 0 0 / 0.3) light, oklab(0.556 0 0 / 0.3) dark
vega: [button,a]:focus-visible:ring-ring/50 → oklab(0.708 0 0 / 0.5) light, oklab(0.556 0 0 / 0.5) dark
Keyboard focus (Tab) on the button bubble, read after the transition settles; border-color matches (border-ring). Screens: screens/bubble-focus-{rubyui,shadcn}-{light,dark}.png. Requested: use ring-ring/50 on the BubbleContent focus ring.
↳ style-vega.css:1464
❌ Look Bubble: no group-data-[align=end]/message:self-end
vega: cn-bubble: group-data-[align=end]/message:self-end
Browser, fixture case Message(align: :end) > div.flex.flex-col.w-full > Bubble: the RubyUI bubble stays at the start (222px gap on the right), shadcn moves it to the end (0px). In Message > MessageContent both sides already end-align, because MessageContent (like vega cn-message-content, style-vega.css:1758) pushes slotted children to the end.
↳ style-vega.css:1460
✅ Look BubbleGroup, Bubble, BubbleReactions (side top/bottom, align start/end) and the seven variant backgrounds/text colors
Variant class strings in Bubble::VARIANTS are identical to cn-bubble-variant-* (style-vega.css:1467-1493); hover states not driven.
↳ computed styles (shadcn-audit collect)
✅ HTML div[data-slot=bubble-group] > div[data-slot=bubble][data-variant][data-align] > div|button[type=button][data-slot=bubble-content] + div[data-slot=bubble-reactions][data-side][data-align]
Twelve bubbles (two inside an end-aligned Message), same data-variant/data-align/data-side on each.
↳ rendered DOM (both fixtures)
✅ Props Bubble variant:, align:; BubbleContent as:; BubbleReactions side:, align:
BubbleContent render={<button />} maps to as: :button (tag only; other attributes pass through **attrs). Defaults match: variant default, align start; reactions side bottom, align end.
↳ bases/base/ui/bubble.tsx:37
✅ Behavior button bubble reachable by Tab, :focus-visible applies
Static component; no JS on either side.
↳ browser run (Playwright, 1280×800)
✅ Native no Stimulus controller; interactive content is a native <button type=button> — native
↳ https://developer.mozilla.org/pt-BR/docs/Web/HTML/Element/button
✅ Spec button bubble shows a 3px focus ring
Both meet 2.4.7 Focus Visible; shadcn side checked first.
↳ https://www.w3.org/TR/WCAG22/#focus-visible
shadcn audit, this PR
shadcn audit ruby-ui/ruby_ui
base 27edd81c head ad21ba8e shadcn c0d63c33
⚠️ Environment font: "Inter Variable", "system-ui", -apple-system, "system-ui", "Segoe UI", Roboto, "Helvetica Neue", Arial, "sans-serif" vs "Inter Variable", sans-serif (light)
⚠️ Environment font: "Inter Variable", "system-ui", -apple-system, "system-ui", "Segoe UI", Roboto, "Helvetica Neue", Arial, "sans-serif" vs "Inter Variable", sans-serif (dark)
⚠️ Environment RubyUI preview boot: the global Ruby 3.4.7 on macmini has rdoc 8.1.0, whose RubyGems plugin activates date 3.6.0 before Bundler; the throwaway preview lockfile was bumped from date 3.5.1 to 3.6.0 to boot. No effect on rendering.
✅ Theme neutral tokens match shadcn
Bubble (ref: bases/base/ui/bubble.tsx + style-vega.css @ c0d63c33)
⏸ Look properties: font-family
BubbleGroup, Bubble, BubbleContent, BubbleReactions — explained by environment diff: font. Both sides resolve to Inter Variable first; only the fallback stack differs. Kept as is: environment.
✅ ARIA BubbleGroup, Bubble, BubbleContent, BubbleReactions: accessibility tree matches
↳ accessibility tree (shadcn-audit collect)
✅ Look BubbleContent rounded-xl py-2 (14px radius, 8px block padding); focus-visible ring-ring/50 → oklab(0.708 0 0 / 0.5) light, oklab(0.556 0 0 / 0.5) dark
Computed styles closed light/dark, and keyboard focus (Tab) after the transition settles.
↳ style-vega.css:1464
✅ Look Bubble group-data-[align=end]/message:self-end; in Message(align: :end) > div.flex.flex-col.w-full the bubble ends at the edge (0px gap), and in Message > MessageContent too
New fixture cases, light and dark. On main the plain-column case leaves a 222px gap.
↳ style-vega.css:1460
✅ Look BubbleGroup, Bubble, BubbleReactions (side top/bottom, align start/end) and the seven variant backgrounds/text colors
Variant class strings in Bubble::VARIANTS are identical to cn-bubble-variant-* (style-vega.css:1467-1493); hover states not driven.
↳ computed styles (shadcn-audit collect)
✅ HTML div[data-slot=bubble-group] > div[data-slot=bubble][data-variant][data-align] > div|button[type=button][data-slot=bubble-content] + div[data-slot=bubble-reactions][data-side][data-align]
Twelve bubbles (two inside an end-aligned Message), same data-variant/data-align/data-side on each.
↳ rendered DOM (both fixtures)
✅ Props Bubble variant:, align:; BubbleContent as:; BubbleReactions side:, align:
BubbleContent render={<button />} maps to as: :button (tag only; other attributes pass through **attrs). Defaults match: variant default, align start; reactions side bottom, align end.
↳ bases/base/ui/bubble.tsx:37
✅ Behavior button bubble reachable by Tab, :focus-visible applies
Static component; no JS on either side.
↳ browser run (Playwright, 1280×800)
✅ Native no Stimulus controller; interactive content is a native <button type=button> — native
↳ https://developer.mozilla.org/pt-BR/docs/Web/HTML/Element/button
✅ Spec button bubble shows a 3px focus ring
Both meet 2.4.7 Focus Visible; shadcn side checked first.
↳ https://www.w3.org/TR/WCAG22/#focus-visible
🤖 Generated with Claude Code
Summary by cubic
Restyles Bubble to match shadcn's Base UI + vega Bubble, so the shadcn audit finds no Look, HTML, Props or ARIA differences.
- BubbleContent uses
rounded-xl py-2instead ofrounded-3xl py-2.5: bubbles get 14px corners and 8px vertical padding instead of 24px and 10px. - The keyboard focus ring on button/link BubbleContent is now
ring-ring/50instead ofring-ring/30, making it clearly visible. - Bubble now end-aligns itself inside
Message(align: :end)viagroup-data-[align=end]/message:self-end, so it works even when not nested inMessageContent. - Adds Bubble to the shadcn audit with fixtures covering all seven variants, reactions, a button bubble, and end-aligned messages.
- Documents the end-aligned Message case under Alignment and adds a test for it.
Migration
Apps that copied the component can keep the old pill look by passing class: "rounded-3xl py-2.5" to BubbleContent.