Skip to content

Commit cb121bc

Browse files
fix: add defensive styles to prevent global CSS pollution (#214)
1 parent 529274d commit cb121bc

File tree

16 files changed

+293
-160
lines changed

16 files changed

+293
-160
lines changed

.changeset/defensive-styles.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
"@cloudflare/kumo": patch
3+
---
4+
5+
fix: add defensive styles to prevent global CSS pollution
6+
7+
## Problem
8+
9+
When Kumo components are used in applications with aggressive global styles (e.g., Stratus's `cfBaseStyles` layer), certain elements get polluted:
10+
11+
- `label { margin-bottom: 1rem }` adds unwanted margins to all labels
12+
- `button { background: gray }` affects unstyled button wrappers (e.g., tooltip triggers)
13+
- `a { color: var(--text-color-primary) }` can override link colors if the consuming app defines `--text-color-primary` differently
14+
15+
## Solution
16+
17+
Add defensive Tailwind utility classes directly to components. These:
18+
19+
1. Reset commonly-polluted properties to safe defaults
20+
2. Use `cn()` (tailwind-merge) so consumer styles via `className` still override them
21+
3. Are no-ops in clean CSS environments (no visual change in Kumo docs)
22+
23+
## Changes
24+
25+
### Label margins (`m-0`)
26+
27+
- **Label**: `labelVariants()` now includes `m-0`
28+
- **Field**: `FieldBase.Label` gets `m-0`
29+
- **Checkbox**: label wrapper gets `m-0`
30+
- **Radio**: label wrapper gets `m-0`
31+
- **Switch**: label wrapper gets `m-0`
32+
33+
### Button trigger resets
34+
35+
- **Tooltip trigger** (when `!asChild`): `bg-transparent border-none shadow-none p-0 m-0 h-auto min-h-0 leading-[0] inline-flex items-center`
36+
- **Collapsible trigger**: `bg-transparent border-none shadow-none p-0 m-0`
37+
38+
### Link color namespace fix
39+
40+
- **Link**: Changed from `text-primary` to `text-kumo-link` to avoid collision with consuming apps that define `--text-color-primary` differently
41+
42+
### Label tooltip composition
43+
44+
- **Label**: Tooltip trigger now uses `<Button variant="ghost" size="xs" shape="square">` with `asChild`, leveraging composition instead of relying on defensive resets
45+
46+
## Docs
47+
48+
Added "Custom Trigger" section to Tooltip docs demonstrating that `className` can fully override defensive styles when not using `asChild`.

packages/kumo-docs-astro/src/components/demos/TooltipDemo.tsx

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Tooltip, TooltipProvider, Button } from "@cloudflare/kumo";
2-
import { PlusIcon, TranslateIcon } from "@phosphor-icons/react";
2+
import { Info, PlusIcon, TranslateIcon } from "@phosphor-icons/react";
33

44
export function TooltipHeroDemo() {
55
return (
@@ -39,3 +39,22 @@ export function TooltipMultipleDemo() {
3939
</TooltipProvider>
4040
);
4141
}
42+
43+
/**
44+
* Without `asChild`, Tooltip wraps children in an internal button element.
45+
* Defensive styles are applied by default, but you can fully customize
46+
* the trigger by passing className - your styles override the defaults.
47+
*/
48+
export function TooltipCustomTriggerDemo() {
49+
return (
50+
<TooltipProvider>
51+
<Tooltip
52+
content="Click to learn more"
53+
className="inline-flex items-center gap-1.5 rounded-full bg-kumo-brand px-3 py-1.5 text-sm font-medium text-white shadow-md transition-transform hover:scale-105 active:scale-95"
54+
>
55+
<Info className="size-4" />
56+
<span>Help</span>
57+
</Tooltip>
58+
</TooltipProvider>
59+
);
60+
}

packages/kumo-docs-astro/src/pages/components/tooltip.astro

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import ComponentSection from "../../components/docs/ComponentSection.astro";
55
import ComponentExample from "../../components/docs/ComponentExample.astro";
66
import CodeBlock from "../../components/docs/CodeBlock.astro";
77
import PropsTable from "../../components/docs/PropsTable.astro";
8-
import { TooltipHeroDemo, TooltipBasicDemo, TooltipMultipleDemo } from "../../components/demos/TooltipDemo";
8+
import { TooltipHeroDemo, TooltipBasicDemo, TooltipMultipleDemo, TooltipCustomTriggerDemo } from "../../components/demos/TooltipDemo";
99
---
1010

1111
<DocLayout
@@ -80,6 +80,24 @@ export default function Example() {
8080
</div>
8181
</ComponentSection>
8282

83+
<ComponentSection>
84+
<Heading level={2} class="mb-6">Custom Trigger</Heading>
85+
<p class="mb-4 text-sm text-kumo-subtle">
86+
We recommend using <code>asChild</code> with a Kumo Button (as shown above). However, when <code>asChild</code> is omitted,
87+
Tooltip wraps children in an internal button element. Defensive styles are applied to protect this element from
88+
global CSS pollution. You can override these defaults via <code>className</code>.
89+
</p>
90+
<ComponentExample code={`<Tooltip
91+
content="Click to learn more"
92+
className="inline-flex items-center gap-1.5 rounded-full bg-kumo-brand px-3 py-1.5 text-sm font-medium text-white shadow-md transition-transform hover:scale-105 active:scale-95"
93+
>
94+
<Info className="size-4" />
95+
<span>Help</span>
96+
</Tooltip>`}>
97+
<TooltipCustomTriggerDemo client:load />
98+
</ComponentExample>
99+
</ComponentSection>
100+
83101
<ComponentSection>
84102
<Heading level={2}>API Reference</Heading>
85103
<PropsTable component="Tooltip" />

0 commit comments

Comments
 (0)