@fpkit/acss 6.2.0 → 6.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/libs/chunk-25KCUE3R.cjs +17 -0
  2. package/libs/chunk-25KCUE3R.cjs.map +1 -0
  3. package/libs/chunk-34NWHFHP.js +10 -0
  4. package/libs/chunk-34NWHFHP.js.map +1 -0
  5. package/libs/{chunk-SQ44OCJ2.js → chunk-6NMLU5FA.js} +2 -2
  6. package/libs/{chunk-GVVCXXKI.cjs → chunk-6YVR4TDM.cjs} +3 -3
  7. package/libs/chunk-DSQ2TUCR.js +7 -0
  8. package/libs/chunk-DSQ2TUCR.js.map +1 -0
  9. package/libs/{chunk-H6A2CUWA.js → chunk-VQTCTLFN.js} +2 -2
  10. package/libs/chunk-ZJ4RUKI2.cjs +14 -0
  11. package/libs/chunk-ZJ4RUKI2.cjs.map +1 -0
  12. package/libs/{chunk-H4JRUNKU.cjs → chunk-ZOPHCNFD.cjs} +3 -3
  13. package/libs/components/button.cjs +3 -3
  14. package/libs/components/button.d.cts +34 -1
  15. package/libs/components/button.d.ts +34 -1
  16. package/libs/components/button.js +1 -1
  17. package/libs/components/buttons/button.css +1 -1
  18. package/libs/components/buttons/button.css.map +1 -1
  19. package/libs/components/buttons/button.min.css +2 -2
  20. package/libs/components/buttons/icon-button.css +1 -0
  21. package/libs/components/buttons/icon-button.css.map +1 -0
  22. package/libs/components/buttons/icon-button.min.css +3 -0
  23. package/libs/components/dialog/dialog.cjs +4 -4
  24. package/libs/components/dialog/dialog.js +2 -2
  25. package/libs/components/icons/icon.d.cts +1 -1
  26. package/libs/components/icons/icon.d.ts +1 -1
  27. package/libs/components/link/link.css +1 -1
  28. package/libs/components/link/link.min.css +1 -1
  29. package/libs/components/modal.cjs +3 -3
  30. package/libs/components/modal.js +2 -2
  31. package/libs/components/popover/popover.cjs +3 -8
  32. package/libs/components/popover/popover.css +1 -0
  33. package/libs/components/popover/popover.css.map +1 -0
  34. package/libs/components/popover/popover.d.cts +54 -26
  35. package/libs/components/popover/popover.d.ts +54 -26
  36. package/libs/components/popover/popover.js +1 -2
  37. package/libs/components/popover/popover.min.css +3 -0
  38. package/libs/hooks.cjs +3 -6
  39. package/libs/hooks.cjs.map +1 -1
  40. package/libs/hooks.d.cts +30 -10
  41. package/libs/hooks.d.ts +30 -10
  42. package/libs/hooks.js +5 -1
  43. package/libs/hooks.js.map +1 -1
  44. package/libs/{icons-48788561.d.ts → icons-2c09535c.d.ts} +32 -32
  45. package/libs/icons.d.cts +1 -1
  46. package/libs/icons.d.ts +1 -1
  47. package/libs/index.cjs +35 -35
  48. package/libs/index.cjs.map +1 -1
  49. package/libs/index.css +1 -1
  50. package/libs/index.css.map +1 -1
  51. package/libs/index.d.cts +64 -5
  52. package/libs/index.d.ts +64 -5
  53. package/libs/index.js +9 -10
  54. package/libs/index.js.map +1 -1
  55. package/package.json +2 -2
  56. package/src/components/buttons/README.mdx +107 -11
  57. package/src/components/buttons/STYLES.mdx +182 -47
  58. package/src/components/buttons/button.scss +93 -16
  59. package/src/components/buttons/button.stories.tsx +149 -0
  60. package/src/components/buttons/button.test.tsx +12 -0
  61. package/src/components/buttons/button.tsx +50 -6
  62. package/src/components/buttons/icon-button.scss +45 -0
  63. package/src/components/buttons/icon-button.stories.tsx +200 -0
  64. package/src/components/buttons/icon-button.test.tsx +132 -0
  65. package/src/components/buttons/icon-button.tsx +72 -0
  66. package/src/components/form/select.tsx +55 -51
  67. package/src/components/link/link.scss +2 -2
  68. package/src/components/popover/README.mdx +478 -0
  69. package/src/components/popover/STYLES.mdx +389 -0
  70. package/src/components/popover/index.ts +3 -0
  71. package/src/components/popover/popover.scss +249 -0
  72. package/src/components/popover/popover.stories.tsx +315 -15
  73. package/src/components/popover/popover.test.tsx +249 -37
  74. package/src/components/popover/popover.tsx +165 -62
  75. package/src/hooks/popover/popover.tsx +26 -10
  76. package/src/hooks/popover/use-popover.tsx +30 -10
  77. package/src/hooks.ts +5 -0
  78. package/src/index.scss +1 -0
  79. package/src/index.ts +1 -0
  80. package/src/styles/buttons/button.css +78 -16
  81. package/src/styles/buttons/button.css.map +1 -1
  82. package/src/styles/buttons/icon-button.css +32 -0
  83. package/src/styles/buttons/icon-button.css.map +1 -0
  84. package/src/styles/index.css +268 -18
  85. package/src/styles/index.css.map +1 -1
  86. package/src/styles/link/link.css +2 -2
  87. package/src/styles/popover/popover.css +190 -0
  88. package/src/styles/popover/popover.css.map +1 -0
  89. package/src/types/popover.d.ts +64 -0
  90. package/libs/chunk-4I5MF54P.js +0 -8
  91. package/libs/chunk-4I5MF54P.js.map +0 -1
  92. package/libs/chunk-GCGKYLDG.js +0 -7
  93. package/libs/chunk-GCGKYLDG.js.map +0 -1
  94. package/libs/chunk-NZVSXRTB.cjs +0 -16
  95. package/libs/chunk-NZVSXRTB.cjs.map +0 -1
  96. package/libs/chunk-PDD4N5P5.cjs +0 -10
  97. package/libs/chunk-PDD4N5P5.cjs.map +0 -1
  98. package/libs/chunk-S7NIA6PI.cjs +0 -17
  99. package/libs/chunk-S7NIA6PI.cjs.map +0 -1
  100. package/libs/chunk-X2RDXWH5.js +0 -10
  101. package/libs/chunk-X2RDXWH5.js.map +0 -1
  102. /package/libs/{chunk-SQ44OCJ2.js.map → chunk-6NMLU5FA.js.map} +0 -0
  103. /package/libs/{chunk-GVVCXXKI.cjs.map → chunk-6YVR4TDM.cjs.map} +0 -0
  104. /package/libs/{chunk-H6A2CUWA.js.map → chunk-VQTCTLFN.js.map} +0 -0
  105. /package/libs/{chunk-H4JRUNKU.cjs.map → chunk-ZOPHCNFD.cjs.map} +0 -0
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@fpkit/acss",
3
3
  "description": "A lightweight React UI library for building modern and accessible components that leverage CSS custom properties for reactive Styles.",
4
4
  "private": false,
5
- "version": "6.2.0",
5
+ "version": "6.3.0",
6
6
  "engines": {
7
7
  "node": ">=22.12.0",
8
8
  "npm": ">=8.0.0"
@@ -123,5 +123,5 @@
123
123
  "publishConfig": {
124
124
  "access": "public"
125
125
  },
126
- "gitHead": "6bdffe9311776c02c96d2592d65123b4b6c0e92a"
126
+ "gitHead": "267091cf42709bbb5e74aa9b1d75bc2ced157b04"
127
127
  }
@@ -18,16 +18,21 @@ supports various types and styles. It is designed to be reusable and accessible.
18
18
 
19
19
  ## Props
20
20
 
21
- | Name | Type | Default | Description |
22
- | ---------------- | ---------------------------------------------------- | ---------- | -------------------------------------- |
23
- | `type` | `'button' \| 'submit' \| 'reset'` | `'button'` | The button type |
24
- | `styles` | `React.CSSProperties` | - | Inline styles for the button |
25
- | `disabled` | `boolean` | `false` | Disables the button when set to `true` |
26
- | `classes` | `string` | - | Additional CSS classes for the button |
27
- | `onPointerDown` | `(e: React.PointerEvent<HTMLButtonElement>) => void` | - | Callback for pointer down event |
28
- | `onPointerOver` | `(e: React.PointerEvent<HTMLButtonElement>) => void` | - | Callback for pointer over event |
29
- | `onPointerLeave` | `(e: React.PointerEvent<HTMLButtonElement>) => void` | - | Callback for pointer leave event |
30
- | `onClick` | `(e: React.MouseEvent<HTMLButtonElement>) => void` | - | Callback for click event |
21
+ | Name | Type | Default | Description |
22
+ | ---------------- | ----------------------------------------------------------------- | ---------- | ---------------------------------------------------------------------------------- |
23
+ | `type` | `'button' \| 'submit' \| 'reset'` | `'button'` | The button type |
24
+ | `size` | `'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| '2xl'` | - | Size variant merged into `data-btn` |
25
+ | `variant` | `'text' \| 'pill' \| 'icon' \| 'outline'` | - | Style variant maps to `data-style` |
26
+ | `color` | `'primary' \| 'secondary' \| 'danger' \| 'success' \| 'warning'` | - | Color variant using semantic tokens — maps to `data-color` |
27
+ | `block` | `boolean` | - | Stretches button to 100% container width — merged into `data-btn` as `"block"` |
28
+ | `"data-btn"` | `string` | - | Raw `data-btn` tokens. Merged with `size` and `block` into the final attribute. `data-btn` tokens take precedence over `size` tokens — if both conflict, `data-btn` wins. |
29
+ | `styles` | `React.CSSProperties` | - | Inline styles use to override CSS custom properties e.g. `--btn-bg` |
30
+ | `disabled` | `boolean` | `false` | Disables the button using the `aria-disabled` pattern (stays keyboard-focusable) |
31
+ | `classes` | `string` | - | Additional CSS classes (merged automatically with `.is-disabled` when disabled) |
32
+ | `onPointerDown` | `(e: React.PointerEvent<HTMLButtonElement>) => void` | - | Callback for pointer down event |
33
+ | `onPointerOver` | `(e: React.PointerEvent<HTMLButtonElement>) => void` | - | Callback for pointer over event |
34
+ | `onPointerLeave` | `(e: React.PointerEvent<HTMLButtonElement>) => void` | - | Callback for pointer leave event |
35
+ | `onClick` | `(e: React.MouseEvent<HTMLButtonElement>) => void` | - | Callback for click event |
31
36
 
32
37
  ## Technical Details
33
38
 
@@ -56,6 +61,85 @@ const BasicButton = () => (
56
61
  export default BasicButton;
57
62
  ```
58
63
 
64
+ ### Size, Variant & Color Props
65
+
66
+ The `size`, `variant`, and `color` props provide a typed React API over the
67
+ underlying `data-*` attributes. This gives consumers autocomplete and type
68
+ safety without needing to know the attribute names.
69
+
70
+ ```tsx
71
+ import Button from "#components/buttons/button";
72
+
73
+ // Size variants
74
+ <Button type="button" size="xs">Extra Small</Button>
75
+ <Button type="button" size="sm">Small</Button>
76
+ <Button type="button" size="lg">Large</Button>
77
+ <Button type="button" size="xl">Extra Large</Button>
78
+ <Button type="button" size="2xl">2X Large</Button>
79
+
80
+ // Style variants
81
+ <Button type="button" variant="outline">Outline</Button>
82
+ <Button type="button" variant="pill">Pill</Button>
83
+ <Button type="button" variant="text">Text Button</Button>
84
+ <Button type="button" variant="icon" aria-label="Close">✕</Button>
85
+
86
+ // Color variants (use semantic design tokens)
87
+ <Button type="button" color="primary">Primary</Button>
88
+ <Button type="button" color="secondary">Secondary</Button>
89
+ <Button type="button" color="danger">Delete</Button>
90
+ <Button type="button" color="success">Confirm</Button>
91
+ <Button type="button" color="warning">Warning</Button>
92
+
93
+ // Combine props freely
94
+ <Button type="button" color="primary" variant="outline" size="lg">
95
+ Large Primary Outline
96
+ </Button>
97
+ <Button type="button" color="danger" variant="pill">Danger Pill</Button>
98
+ ```
99
+
100
+ **Note:** `size`, `block`, and `data-btn` are all merged into a single
101
+ whitespace-separated `data-btn` attribute. `<Button size="lg" block data-btn="pill">`
102
+ resolves to `data-btn="lg block pill"`.
103
+
104
+ ### Block Layout
105
+
106
+ The `block` prop stretches the button to 100% of its container width. It
107
+ composes freely with `size` and other `data-btn` tokens.
108
+
109
+ ```tsx
110
+ <Button type="button" block>Full Width</Button>
111
+ <Button type="button" size="lg" block color="primary">Large Full Width Primary</Button>
112
+ ```
113
+
114
+ ### IconButton
115
+
116
+ `IconButton` wraps `Button` with enforced accessibility and icon-specific defaults.
117
+ It requires exactly one of `aria-label` or `aria-labelledby` (XOR — passing
118
+ both or neither is a TypeScript compile-time error).
119
+
120
+ ```tsx
121
+ import { IconButton } from "@fpkit/acss";
122
+
123
+ // Icon only
124
+ <IconButton type="button" aria-label="Close menu" icon={<CloseIcon />} />
125
+
126
+ // Icon + visible label (label hides on mobile, stays in a11y tree)
127
+ <IconButton
128
+ type="button"
129
+ aria-label="Settings"
130
+ icon={<SettingsIcon />}
131
+ label="Settings"
132
+ variant="outline"
133
+ />
134
+
135
+ // Labelled by external element
136
+ <span id="btn-lbl">Delete item</span>
137
+ <IconButton type="button" aria-labelledby="btn-lbl" icon={<TrashIcon />} />
138
+ ```
139
+
140
+ **Defaults:** `variant="icon"` (transparent bg, `currentColor` icon color, square touch target).
141
+ Override `variant` to restore padding when using a `label`.
142
+
59
143
  ### Advanced Usage
60
144
 
61
145
  ```tsx
@@ -77,7 +161,8 @@ const handlePointerLeave = (e: React.PointerEvent<HTMLButtonElement>) => {
77
161
  const AdvancedButton = () => (
78
162
  <Button
79
163
  type="submit"
80
- styles={{ backgroundColor: "blue", color: "white" }}
164
+ color="primary"
165
+ size="lg"
81
166
  onPointerDown={handlePointerDown}
82
167
  onPointerOver={handlePointerOver}
83
168
  onPointerLeave={handlePointerLeave}
@@ -150,6 +235,10 @@ When `disabled={true}`:
150
235
  - ✅ Hover effects still work (visual feedback)
151
236
  - ✅ Can receive focus for tooltips/help text
152
237
 
238
+ ### Handler Precedence When Disabled
239
+
240
+ When `disabled` or `isDisabled` is set, built-in pointer handlers (`onPointerDown`, `onPointerOver`, `onPointerLeave`, `onClick`) take precedence over any handlers passed via props. This prevents interaction callbacks from firing on a disabled button.
241
+
153
242
  ### Migration Guide
154
243
 
155
244
  No breaking changes! The API remains the same:
@@ -205,3 +294,10 @@ This implementation follows:
205
294
  - The `styles` prop can be used to apply inline styles to the button.
206
295
  - The `disabled` prop uses the accessible `aria-disabled` pattern instead of
207
296
  native `disabled` attribute.
297
+ - **Breaking (height):** The `--btn-height` multiplier changed from `2.25` to
298
+ `2.75`. All buttons are ~22% taller. Override `--btn-height` via the `styles`
299
+ prop or a stylesheet rule to restore previous sizing.
300
+ - **Breaking (`.btn-pill`):** The `.btn-pill` CSS class is now scoped to
301
+ `button.btn-pill` only. Non-button elements carrying the class no longer
302
+ receive pill border-radius. Prefer `variant="pill"` (React) or
303
+ `data-style="pill"` / `data-btn="pill"` (HTML) instead.
@@ -16,8 +16,9 @@ accessibility.
16
16
 
17
17
  ### Key Features
18
18
 
19
- - **Size variants** - Extra small (xs) through large (lg) with fluid typography
20
- - **Style variants** - Pill, icon, and text button styles
19
+ - **Size variants** - Extra small (xs) through 2X large (2xl) with fluid typography
20
+ - **Style variants** - Pill, outline, icon, and text button styles via `data-style`
21
+ - **Color variants** - Semantic color tokens (primary, secondary, danger, success, warning) via `data-color`
21
22
  - **Type-based styling** - Automatic styling for submit, reset button types
22
23
  - **State management** - Hover, focus, and disabled states with smooth
23
24
  transitions
@@ -37,13 +38,15 @@ button {
37
38
  --btn-size-xs: 0.6875rem; /* 11px */
38
39
  --btn-size-sm: 0.8125rem; /* 13px */
39
40
  --btn-size-md: 0.9375rem; /* 15px */
40
- --btn-size-lg: 1.125rem; /* 18px */
41
+ --btn-size-lg: 1.125rem; /* 18px */
42
+ --btn-size-xl: 1.375rem; /* 22px */
43
+ --btn-size-2xl: 1.75rem; /* 28px */
41
44
 
42
45
  /* Default font size */
43
46
  --btn-fs: var(--btn-size-md);
44
47
 
45
48
  /* Dynamic height based on font size */
46
- --btn-height: calc(var(--btn-fs) * 2.25);
49
+ --btn-height: calc(var(--btn-fs) * 2.75); /* ⚠️ changed from * 2.25, see CHANGELOG */
47
50
  }
48
51
  ```
49
52
 
@@ -56,8 +59,8 @@ button {
56
59
  --btn-fw: 500; /* Font weight */
57
60
 
58
61
  /* Colors */
59
- --btn-bg: lightgray; /* Background color */
60
- --btn-color: currentColor; /* Text color */
62
+ --btn-bg: var(--color-primary); /* Background color — ⚠️ changed from color-neutral-300, see CHANGELOG */
63
+ --btn-color: var(--color-text-inverse); /* Text color */
61
64
 
62
65
  /* Spacing */
63
66
  --btn-padding-inline: calc(var(--btn-fs) * 1.5); /* Horizontal padding */
@@ -71,7 +74,7 @@ button {
71
74
 
72
75
  /* Layout */
73
76
  --btn-width: max-content; /* Button width */
74
- --btn-height: calc(var(--btn-fs) * 2.25); /* Button height */
77
+ --btn-height: calc(var(--btn-fs) * 2.75); /* Button height */
75
78
  --btn-display: inline-flex; /* Display mode */
76
79
  --btn-place: center; /* place-items value */
77
80
 
@@ -114,7 +117,7 @@ button[aria-disabled="true"] {
114
117
  ```css
115
118
  button {
116
119
  /* Pill variant */
117
- --btn-pill: 100rem; /* Fully rounded borders */
120
+ --btn-pill: 100vw; /* Fully rounded borders */
118
121
  }
119
122
  ```
120
123
 
@@ -153,12 +156,12 @@ Standard button with default styling:
153
156
  button {
154
157
  font-size: var(--btn-fs); /* 0.9375rem / 15px */
155
158
  font-weight: 500;
156
- height: calc(var(--btn-fs) * 2.25);
159
+ height: calc(var(--btn-fs) * 2.75);
157
160
  padding-inline: calc(var(--btn-fs) * 1.5);
158
161
  padding-block: calc(var(--btn-fs) * 0.5);
159
162
  border-radius: 0.375rem;
160
- background-color: lightgray;
161
- color: currentColor;
163
+ background-color: var(--color-primary); /* ⚠️ breaking change — was color-neutral-300 */
164
+ color: var(--color-text-inverse);
162
165
  display: inline-flex;
163
166
  align-items: center;
164
167
  gap: 0.2rem;
@@ -206,12 +209,15 @@ button[type="reset"] {
206
209
 
207
210
  Control button size using data attributes or classes:
208
211
 
209
- | Data Attribute | Class | Font Size | Pixel Equivalent | Description |
210
- | --------------- | --------- | ----------- | ---------------- | ------------------ |
211
- | `data-btn="xs"` | `.btn-xs` | `0.6875rem` | 11px | Extra small button |
212
- | `data-btn="sm"` | `.btn-sm` | `0.8125rem` | 13px | Small button |
213
- | `data-btn="md"` | `.btn-md` | `0.9375rem` | 15px | Medium (default) |
214
- | `data-btn="lg"` | `.btn-lg` | `1.125rem` | 18px | Large button |
212
+ | Data Attribute | Class | Font Size | Pixel Equivalent | Description |
213
+ | ----------------- | ----------- | ----------- | ---------------- | --------------------- |
214
+ | `data-btn="xs"` | `.btn-xs` | `0.6875rem` | 11px | Extra small button |
215
+ | `data-btn="sm"` | `.btn-sm` | `0.8125rem` | 13px | Small button |
216
+ | `data-btn="md"` | `.btn-md` | `0.9375rem` | 15px | Medium (default) |
217
+ | `data-btn="lg"` | `.btn-lg` | `1.125rem` | 18px | Large button |
218
+ | `data-btn="xl"` | `.btn-xl` | `1.375rem` | 22px | Extra large button |
219
+ | `data-btn="2xl"` | `.btn-2xl` | `1.75rem` | 28px | 2X large button |
220
+ | `data-btn="block"`| `.btn-block`| — | — | 100% container width |
215
221
 
216
222
  ### Examples
217
223
 
@@ -228,6 +234,12 @@ Control button size using data attributes or classes:
228
234
 
229
235
  <!-- Large -->
230
236
  <button data-btn="lg">Large Button</button>
237
+
238
+ <!-- Extra Large -->
239
+ <button data-btn="xl">Extra Large Button</button>
240
+
241
+ <!-- 2X Large -->
242
+ <button data-btn="2xl">2X Large Button</button>
231
243
  ```
232
244
 
233
245
  ### Size Customization
@@ -237,14 +249,75 @@ Height and padding scale automatically based on font size:
237
249
  ```css
238
250
  button[data-btn~="lg"] {
239
251
  --btn-fs: 1.125rem; /* 18px */
240
- --btn-height: calc(1.125rem * 2.25); /* 2.53125rem / 40.5px */
252
+ --btn-height: calc(1.125rem * 2.75); /* ~3.09rem / ~49.5px */
241
253
  --btn-padding-inline: calc(1.125rem * 1.5); /* 1.6875rem / 27px */
242
254
  --btn-padding-block: calc(1.125rem * 0.5); /* 0.5625rem / 9px */
243
255
  }
244
256
  ```
245
257
 
258
+ ## Color Variants
259
+
260
+ Color variants map to semantic design tokens defined in `index.css`. Using
261
+ `data-color` (or the `color` prop in React) keeps button colors consistent with
262
+ the global color system — no hardcoded hex values needed.
263
+
264
+ | `data-color` value | Background token | Text token |
265
+ | ------------------ | ----------------- | ----------------------- |
266
+ | `primary` | `--color-primary` | `--color-text-inverse` |
267
+ | `secondary` | `--color-secondary` | `--color-text-inverse` |
268
+ | `danger` | `--color-error` | `--color-text-inverse` |
269
+ | `success` | `--color-success` | `--color-text-inverse` |
270
+ | `warning` | `--color-warning` | `--color-text-inverse` |
271
+
272
+ ### HTML Usage
273
+
274
+ ```html
275
+ <button data-color="primary">Primary</button>
276
+ <button data-color="secondary">Secondary</button>
277
+ <button data-color="danger">Delete</button>
278
+ <button data-color="success">Confirm</button>
279
+ <button data-color="warning">Warning</button>
280
+ ```
281
+
282
+ ### React Usage
283
+
284
+ ```tsx
285
+ <Button type="button" color="danger">Delete</Button>
286
+ <Button type="button" color="success" variant="outline">Confirm</Button>
287
+ ```
288
+
289
+ ### CSS
290
+
291
+ ```css
292
+ button[data-color="primary"] {
293
+ --btn-bg: var(--color-primary);
294
+ --btn-color: var(--color-text-inverse);
295
+ }
296
+
297
+ button[data-color="danger"] {
298
+ --btn-bg: var(--color-error);
299
+ --btn-color: var(--color-text-inverse);
300
+ }
301
+ /* etc. */
302
+ ```
303
+
304
+ **Tip:** Color variants compose with style variants. `data-color="primary"` +
305
+ `data-style="outline"` gives you a transparent primary-colored outlined button.
306
+
307
+ ---
308
+
246
309
  ## Style Variants
247
310
 
311
+ Style variants are applied via the `data-style` attribute (or the `variant` prop
312
+ in React). Multiple values can be space-separated.
313
+
314
+ | `data-style` value | Description |
315
+ | ------------------ | ---------------------------------------------- |
316
+ | `pill` | Fully rounded corners (`border-radius: 100rem`) |
317
+ | `outline` | Transparent bg, border, subtle hover |
318
+ | `text` | Ghost button — no bg or border, subtle hover |
319
+ | `icon` | Square icon-only, no padding, min 1.5rem |
320
+
248
321
  ### Pill Button
249
322
 
250
323
  Fully rounded button with large border radius:
@@ -259,10 +332,12 @@ Fully rounded button with large border radius:
259
332
 
260
333
  ```css
261
334
  button[data-btn~="pill"] {
262
- border-radius: var(--btn-pill, 100rem);
335
+ border-radius: var(--btn-pill, 100vw);
263
336
  }
264
337
  ```
265
338
 
339
+ > **⚠️ Breaking change:** The `.btn-pill` CSS class selector was scoped to `button.btn-pill` only. Applying `.btn-pill` to non-`<button>` elements (e.g. `<div>`, `<a>`) no longer sets the border-radius. Use `data-style="pill"` or `data-btn="pill"` attributes instead, which remain element-agnostic.
340
+
266
341
  **Example:**
267
342
 
268
343
  ```html
@@ -304,6 +379,8 @@ button[data-btn~="icon"] {
304
379
  <button data-btn="icon lg" aria-label="Menu">☰</button>
305
380
  ```
306
381
 
382
+ **IconButton label visibility:** When using the `IconButton` React component with the `label` prop, the label text is visually hidden below `48rem` (768px) but remains in the accessibility tree at all viewport sizes.
383
+
307
384
  ### Text Button
308
385
 
309
386
  Minimal button with transparent background and subtle hover:
@@ -337,7 +414,41 @@ button[data-btn~="text"]:is(:hover, :focus) {
337
414
 
338
415
  ```html
339
416
  <button data-btn="text">Learn More</button>
340
- <button data-btn="text" style="--btn-color: #0066cc">View Details</button>
417
+ <button data-style="text" style="--btn-color: #0066cc">View Details</button>
418
+ ```
419
+
420
+ ### Outline Button
421
+
422
+ Transparent background with a visible border and subtle hover effect powered by
423
+ `color-mix()`:
424
+
425
+ ```html
426
+ <button data-style="outline">Outline</button>
427
+ <button data-style="outline" data-color="primary">Primary Outline</button>
428
+ ```
429
+
430
+ **CSS:**
431
+
432
+ ```css
433
+ button[data-style~="outline"] {
434
+ --btn-bg: transparent;
435
+ --btn-color: currentColor;
436
+ --btn-border: 0.125rem solid currentColor;
437
+ }
438
+
439
+ button[data-style~="outline"]:is(:hover, :focus) {
440
+ background-color: color-mix(in srgb, currentColor 10%, transparent);
441
+ outline: 0.025rem solid currentColor;
442
+ outline-offset: 0;
443
+ }
444
+ ```
445
+
446
+ **React:**
447
+
448
+ ```tsx
449
+ <Button type="button" variant="outline">Outline</Button>
450
+ <Button type="button" color="primary" variant="outline">Primary Outline</Button>
451
+ <Button type="button" color="danger" variant="outline">Danger Outline</Button>
341
452
  ```
342
453
 
343
454
  ## State Styles
@@ -439,6 +550,10 @@ You can combine multiple variants using space-separated values:
439
550
  ### Primary Action Button
440
551
 
441
552
  ```html
553
+ <!-- Using semantic color token (recommended) -->
554
+ <button type="submit" data-btn="lg" data-color="primary">Get Started</button>
555
+
556
+ <!-- Or with custom override -->
442
557
  <button
443
558
  type="submit"
444
559
  data-btn="lg"
@@ -451,10 +566,8 @@ You can combine multiple variants using space-separated values:
451
566
  ### Secondary Button
452
567
 
453
568
  ```html
454
- <button
455
- type="button"
456
- style="--btn-bg: transparent; --btn-color: #1976D2; --btn-border: 2px solid #1976D2"
457
- >
569
+ <!-- Outline style using semantic tokens -->
570
+ <button type="button" data-color="primary" data-style="outline">
458
571
  Learn More
459
572
  </button>
460
573
  ```
@@ -462,9 +575,11 @@ You can combine multiple variants using space-separated values:
462
575
  ### Danger Button
463
576
 
464
577
  ```html
465
- <button type="button" style="--btn-bg: #D32F2F; --btn-color: white">
466
- Delete
467
- </button>
578
+ <!-- Semantic token (recommended) -->
579
+ <button type="button" data-color="danger">Delete</button>
580
+
581
+ <!-- React -->
582
+ <!-- <Button type="button" color="danger">Delete</Button> -->
468
583
  ```
469
584
 
470
585
  ### Icon Button Group
@@ -508,8 +623,17 @@ You can combine multiple variants using space-separated values:
508
623
 
509
624
  ### Full Width Button
510
625
 
626
+ Use `data-btn="block"` (or the `block` prop in React) to stretch a button to
627
+ 100% of its container. Composes with size and other tokens.
628
+
511
629
  ```html
512
- <button style="--btn-width: 100%" type="submit">Continue</button>
630
+ <button data-btn="block" type="submit">Continue</button>
631
+ <button data-btn="lg block" type="submit">Large Full Width</button>
632
+ ```
633
+
634
+ ```tsx
635
+ <Button type="submit" block>Continue</Button>
636
+ <Button type="submit" size="lg" block color="primary">Large Full Width Primary</Button>
513
637
  ```
514
638
 
515
639
  ## Accessibility Considerations
@@ -655,7 +779,9 @@ All button CSS variables follow the `--btn-{property}` pattern:
655
779
  --btn-size-xs /* 0.6875rem / 11px */
656
780
  --btn-size-sm /* 0.8125rem / 13px */
657
781
  --btn-size-md /* 0.9375rem / 15px */
658
- --btn-size-lg /* 1.125rem / 18px */
782
+ --btn-size-lg /* 1.125rem / 18px */
783
+ --btn-size-xl /* 1.375rem / 22px */
784
+ --btn-size-2xl /* 1.75rem / 28px */
659
785
  ```
660
786
 
661
787
  ## Browser Support
@@ -732,27 +858,36 @@ Create reusable button classes for common patterns:
732
858
 
733
859
  ### From Tailwind CSS
734
860
 
735
- | Tailwind | fpkit Button |
736
- | ------------------------------- | --------------------------------------------------------------- |
737
- | `class="btn btn-primary"` | `type="submit"` or `style="--btn-bg: blue; --btn-color: white"` |
738
- | `class="btn-sm"` | `data-btn="sm"` |
739
- | `class="btn-lg"` | `data-btn="lg"` |
740
- | `class="rounded-full"` | `data-btn="pill"` |
741
- | `class="btn-ghost"` | `data-btn="text"` |
742
- | `class="btn-circle"` | `data-btn="icon pill"` |
743
- | `class="btn-disabled" disabled` | `disabled` or `aria-disabled="true"` |
861
+ | Tailwind | fpkit Button (HTML) | fpkit Button (React) |
862
+ | ------------------------------- | ---------------------------- | -------------------------------- |
863
+ | `class="btn btn-primary"` | `data-color="primary"` | `color="primary"` |
864
+ | `class="btn-sm"` | `data-btn="sm"` | `size="sm"` |
865
+ | `class="btn-lg"` | `data-btn="lg"` | `size="lg"` |
866
+ | `class="btn-xl"` (custom) | `data-btn="xl"` | `size="xl"` |
867
+ | `class="rounded-full"` | `data-style="pill"` | `variant="pill"` |
868
+ | `class="btn-ghost"` | `data-style="text"` | `variant="text"` |
869
+ | `class="btn-outline"` | `data-style="outline"` | `variant="outline"` |
870
+ | `class="btn-circle"` | `data-style="icon pill"` | `variant="icon"` |
871
+ | `class="btn-error"` | `data-color="danger"` | `color="danger"` |
872
+ | `class="btn-success"` | `data-color="success"` | `color="success"` |
873
+ | `class="btn-disabled" disabled` | `disabled` | `disabled` |
744
874
 
745
875
  ### From Bootstrap
746
876
 
747
- | Bootstrap | fpkit Button |
748
- | --------------------------- | ------------------------------------ |
749
- | `class="btn btn-primary"` | `type="submit"` or custom `--btn-bg` |
750
- | `class="btn btn-secondary"` | `type="reset"` or custom styling |
751
- | `class="btn btn-sm"` | `data-btn="sm"` |
752
- | `class="btn btn-lg"` | `data-btn="lg"` |
753
- | `class="btn rounded-pill"` | `data-btn="pill"` |
754
- | `class="btn btn-link"` | `data-btn="text"` |
755
- | `class="btn" disabled` | `disabled` or `aria-disabled="true"` |
877
+ | Bootstrap | fpkit Button (HTML) | fpkit Button (React) |
878
+ | --------------------------- | --------------------------------- | --------------------------- |
879
+ | `class="btn btn-primary"` | `data-color="primary"` | `color="primary"` |
880
+ | `class="btn btn-secondary"` | `data-color="secondary"` | `color="secondary"` |
881
+ | `class="btn btn-danger"` | `data-color="danger"` | `color="danger"` |
882
+ | `class="btn btn-success"` | `data-color="success"` | `color="success"` |
883
+ | `class="btn btn-sm"` | `data-btn="sm"` | `size="sm"` |
884
+ | `class="btn btn-lg"` | `data-btn="lg"` | `size="lg"` |
885
+ | `class="btn btn-xl"` (n/a) | `data-btn="xl"` | `size="xl"` |
886
+ | `class="btn btn-xxl"` (n/a) | `data-btn="2xl"` | `size="2xl"` |
887
+ | `class="btn rounded-pill"` | `data-style="pill"` | `variant="pill"` |
888
+ | `class="btn btn-outline-*"` | `data-style="outline" data-color` | `variant="outline" color` |
889
+ | `class="btn btn-link"` | `data-style="text"` | `variant="text"` |
890
+ | `class="btn" disabled` | `disabled` | `disabled` |
756
891
 
757
892
  ## Related Resources
758
893