@flood-md/react 0.2.2 → 0.2.4

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.
package/README.md CHANGED
@@ -3,8 +3,8 @@
3
3
  Original React 18.3/19 components for TypeScript and Next.js App Router. Native
4
4
  HTML semantics and refs, no third-party component library or Tailwind dependency.
5
5
 
6
- This package documents the **0.2.2** API. Keep Flood package versions aligned
7
- and follow the guide matching your installed version.
6
+ This package documents the **0.2.4** API. Keep Flood package versions aligned and follow the guide matching
7
+ your installed declarations; source changes do not modify an existing npm release.
8
8
 
9
9
  ```tsx
10
10
  import "@flood-md/react/styles.css";
@@ -30,10 +30,20 @@ included. Legacy controls outside FloodRoot keep their earlier theme behavior.
30
30
 
31
31
  Exports: FloodRoot, Text, Heading, Stack, Inline, Surface, Divider, IconSlot,
32
32
  IconButton, CodeSnippet, Button, Input, Textarea, Checkbox, Radio, Switch, Field and Link,
33
- plus TypeScript prop types. Button variants are solid/subtle/outline/ghost;
33
+ plus TypeScript prop types. Version 0.2.3 also exports Label, Fieldset, RadioGroup, CheckboxGroup, ButtonGroup, InputGroup, Toggle, ToggleGroup, SegmentedControl, Collapsible, Accordion, Tooltip, Popover, Dialog, Tabs, Breadcrumb, Pagination, Select
34
+ and ScrollArea. Button variants are solid/subtle/outline/ghost;
34
35
  sizes are 2xs/xs/sm/md/lg/xl/2xl. Prefer subtle for standalone secondary actions;
35
36
  ghost belongs in a clear parent context. Loading disables activation.
36
37
 
38
+ Version 0.2.3 adds LinkProps with `variant="text" | "solid" | "subtle" |
39
+ "outline" | "ghost"` and the same seven `size` values as Button. The default
40
+ text link keeps its existing appearance and inherits text size; size affects
41
+ only button variants. These variants reuse Button's geometry, tokens and states
42
+ while retaining a real anchor, href, ref, target and browser modifier navigation.
43
+ Use Button for actions; do not add role=button or nest a Button in a Link.
44
+ There is no disabled/loading navigation API. This compatible opt-in extension
45
+ is available in 0.2.3.
46
+
37
47
  Heading adds `size="display"` for large marketing headings: 5.5rem / 1.12,
38
48
  weight 500, tracking -.04em. Existing heading sizes retain their metrics.
39
49
  Text adds `size="lead"` for introductory copy: 1.375rem / 1.45, weight 400,
@@ -44,11 +54,54 @@ font stack; the host owns copy actions and status messages. Both additions are
44
54
  compatible extensions in 0.2.2; they do not replace existing defaults.
45
55
 
46
56
  Input/Textarea controlSize is sm/md/lg/xl; native Input size is preserved.
57
+ Version 0.2.3 adds `status="error" | "warning" | "success" | "info"` to Input,
58
+ Textarea, Select and Field. Use Field's `message` for a description associated
59
+ with the control; spreading its callback props forwards status too. Existing
60
+ `error`, `invalid` or affirmative `aria-invalid` override positive statuses.
61
+ Only error sets aria-invalid; warning, success and info do not reject a value.
62
+ These visual/ARIA states do not replace native constraints or host validation.
63
+
64
+ Input/Textarea `readOnly` preserves focus, selection, copying and form submission
65
+ while preventing edits. Inside dark foundations it uses a canvas background and
66
+ quiet dashed perimeter at rest; focus retains the solid 2px accent. It differs
67
+ from disabled, which prevents ordinary focus and omits the value from submission.
68
+ Native HTML readOnly applies to text-like input types and Textarea; it is not a
69
+ Select or checkbox state. Explain why a value is read only in the surrounding UI.
70
+
71
+ Dark fields use one quiet uniform `color.field.border` perimeter; focus replaces it with a 2px
72
+ accent without a gap or layout shift. Textarea scrolls its content without a
73
+ native resize grip by default; hosts can opt into resizing through the native
74
+ style prop, for example `style={{ resize: "vertical" }}`.
47
75
  Field uses `{props => <Input {...props} />}`. Its required prop forwards native
48
76
  required and displays a marker; describedBy merges external IDs with hint/error
49
77
  associations. Checkbox/Radio/Switch require a visible label. IconSlot accepts
50
78
  host SVG artwork without bundling an icon pack.
51
79
 
80
+ ## Select and choice layout
81
+
82
+ Version 0.2.3 adds **Select**. It accepts native select props/ref, options and optgroups,
83
+ plus `invalid` and `controlSize="sm" | "md" | "lg" | "xl"` (md by default).
84
+ Use Field's callback props on Select to associate the label and descriptions.
85
+ Single selection uses a custom Flood trigger/listbox popup with a native select
86
+ bridge for form values and the HTMLSelectElement ref. Native numeric `size` and
87
+ `multiple` retain an inline listbox without a dropdown chevron or OS popup.
88
+ For single selection, arrows/Home/End highlight without committing; Enter,
89
+ Space or Tab commits, and Escape closes without changing the value. Typeahead
90
+ matches option labels. Focus stays on the trigger; `ref.current.focus()` targets
91
+ it while the ref itself remains the native select. Read changes from
92
+ `event.currentTarget.value` and keep required/disabled/form props on Select.
93
+ Use ordinary option/optgroup children with text labels; an option is not an
94
+ arbitrary interactive slot. See the usage guide for reset and controlled values.
95
+
96
+ Inside FloodRoot, Checkbox/Radio use Flood's custom tick/dot appearance on real
97
+ native inputs. Values, checked/defaultChecked, keyboard, forms and refs remain
98
+ native; forced colors restores native indicators. The glyph stays 1.125rem square,
99
+ with a .5rem label gap and centered alignment. Switch also keeps its label close.
100
+ Use `Inline gap="4"` for a 1rem gap between separate choices; Inline's default
101
+ gap="2" is unchanged. Buttons retain intrinsic width; full width is a deliberate
102
+ host opt-in. See [layout rules](../../docs/layout.md) and
103
+ [Select usage](../../docs/components.md#select).
104
+
52
105
  Import CSS once in Next app/layout.tsx. Add "use client" to host components
53
106
  using state/callbacks or Field's function child. Override semantic --flood-sys-*
54
107
  variables on the scope after the import. Surface overlay is visual elevation,
@@ -57,3 +110,705 @@ not dialog behavior. The focus accent remains configurable and provisional.
57
110
  See [components](../../docs/components.md), [foundations](../../docs/foundations.md)
58
111
  and [Inter provenance](src/fonts/README.md). Verify contrast, focus, keyboard,
59
112
  narrow layouts and text scaling in your actual application.
113
+
114
+
115
+ ## Label and Fieldset
116
+
117
+ Label forwards native label attributes and an HTMLLabelElement ref. Connect its
118
+ htmlFor to a unique control ID, or nest one labelable control. Its required marker
119
+ is decorative; required belongs on the actual control. Field composes this Label.
120
+
121
+ Fieldset forwards native fieldset attributes/ref and takes a required legend.
122
+ It provides hint/message/error/status and merges their IDs with aria-describedby.
123
+ Error wins over advisory feedback. Descriptions belong to the group rather than
124
+ being copied onto every child. Disabled uses native descendant behavior: controls
125
+ in the first legend are exempt. Avoid interactive legend content unless intended.
126
+ Disabled values are omitted from native FormData; nested fieldsets preserve HTML
127
+ rules. Styling removes the browser border but retains the actual fieldset/legend.
128
+ It does not own choice values, implement a selection model or validate children.
129
+
130
+ ```tsx
131
+ <Fieldset legend="Notifications" hint="Choose the updates you want.">
132
+ <Checkbox label="Product updates" name="updates" defaultChecked />
133
+ <Checkbox label="Release notes" name="releases" />
134
+ </Fieldset>
135
+ ```
136
+
137
+ ## ButtonGroup and InputGroup
138
+
139
+ ButtonGroup requires a label and direct Button/IconButton children of compatible
140
+ sizes. It joins visible edges with finite field radii and one full separator along the joined edge, supports
141
+ horizontal/vertical orientation and keeps normal Tab/disabled/action behavior.
142
+ It does not implement a toolbar or selection model. Do not wrap actions in extra
143
+ DOM elements; fragments are fine. Avoid overflow clipping around focused actions.
144
+
145
+ InputGroup accepts text-like Input props plus start/end ReactNode adornments.
146
+ The ref, id, name, value, handlers and ARIA attributes target the actual input;
147
+ className/style target the wrapper. dir/lang/hidden apply to the shell as well.
148
+ Use Field's callback or an associated Label. Adornments are visual and are not
149
+ included in the submitted value or used as a replacement for a label.
150
+ Native required, readOnly and disabled remain input states; host-owned addon
151
+ buttons need their own names, callbacks and disabled policy. Direct Flood Button/IconButton addons inherit their height from the group, with
152
+ an equal space-1 inset against the top, bottom and nearest logical edge; their
153
+ finite radius follows the shell radius minus that inset. Their size prop still
154
+ controls text, icon and horizontal padding. Coarse pointers grow the shell to
155
+ preserve the nested action's touch target. Arbitrary addon content can grow it too.
156
+ Input focus uses the shared perimeter, while an addon action keeps its own focus.
157
+ Use scoped host layout for group width. Textarea/Select require other compositions.
158
+ Both exports are available in 0.2.3.
159
+
160
+ ```tsx
161
+ <Field label="Website">
162
+ {control => <InputGroup {...control} start="https://" placeholder="example.com" />}
163
+ </Field>
164
+ ```
165
+
166
+ ## RadioGroup and CheckboxGroup
167
+
168
+ Both compose Fieldset and the existing native Radio/Checkbox controls. Supply a
169
+ legend, a name for the inputs and an options array with unique string values,
170
+ visible labels and optional disabled flags. The ref targets HTMLFieldSetElement.
171
+ Orientation is vertical by default; horizontal wraps. Disabled, hint, status,
172
+ message and error follow Fieldset. These exports are available in 0.2.3.
173
+ Choice groups use compact sm-height rows without an additional vertical gap.
174
+ Coarse pointers retain the touch-target minimum. Wrapping labels can grow a row;
175
+ the indicator remains explicitly sized. Horizontal groups keep space-4 between
176
+ columns. Legend/helper spacing belongs to the group rather than each option.
177
+
178
+ RadioGroup uses a string value/defaultValue and onValueChange(string).
179
+ CheckboxGroup uses an array value/defaultValue and onValueChange(string[]).
180
+ Omitting value selects uncontrolled mode; otherwise update value in the host.
181
+ Native form reset restores the latest defaultValue in uncontrolled mode and
182
+ preserves the host's controlled value. A canceled reset does nothing; reset does
183
+ not call onValueChange. Values for missing options remain in state but are not
184
+ submitted. Disabled options are omitted from FormData.
185
+
186
+ Name applies to inputs, not the fieldset. CheckboxGroup submits repeated entries;
187
+ read them with FormData.getAll(name). The form prop associates every input with
188
+ an external form. RadioGroup required uses native single-choice validity.
189
+ CheckboxGroup intentionally has no required/min/max prop: validate the array in
190
+ the host and describe errors through status/message. Empty/fully disabled options
191
+ need a host decision about whether submission is meaningful.
192
+
193
+ ```tsx
194
+ <RadioGroup name="visibility" legend="Visibility" defaultValue="private"
195
+ options={[{ value: "private", label: "Private" }, { value: "team", label: "Team" }]} />
196
+ ```
197
+
198
+ ## overlay ScrollArea
199
+
200
+ ScrollArea keeps native wheel, touch and keyboard scrolling while drawing inset
201
+ tracks above content. Set height/maxHeight on its viewport through style; choose
202
+ orientation vertical (default), horizontal or both. The HTMLDivElement ref and
203
+ native div props/className/style target that actual scroll viewport. Supply an
204
+ accessible label when it is a standalone scroll region. Tracks do not reserve
205
+ layout width; inner content padding keeps text clear of them by default. Forced colors and unavailable
206
+ enhancement restore native scrollbars rather than hide scrolling access.
207
+
208
+ Rails automatically shorten near rounded corners on the viewport or a clipping
209
+ ancestor, including asymmetric corners. This does not add a gutter. The default
210
+ `contentInset="scrollbar"` protects text and full-width interactive rows: while
211
+ an overlay rail is present, the inner content receives logical end padding for
212
+ that axis (rail size plus two insets). The viewport keeps its full width; row
213
+ backgrounds and text stop before the rail. Explicit `contentInset="none"` preserves
214
+ edge-to-edge content when the host already provides safe padding. The default
215
+ padding can wrap text and change content height. Native
216
+ fallback scrollbars retain their platform behavior.
217
+
218
+ ```tsx
219
+ <ScrollArea aria-label="Activity" style={{ maxHeight: "12rem" }}>
220
+ <Stack gap="4">{/* Long activity content */}</Stack>
221
+ </ScrollArea>
222
+ ```
223
+
224
+ This export is available in 0.2.3. The website
225
+ uses the same component for code, tables and navigation; Select reuses its internal
226
+ scroll viewport. See [layout](../../docs/layout.md) for constrained compositions.
227
+
228
+ InputGroup start/end are independent slots: use short prefixes (`https://`, `+7`,
229
+ `@`, currency), suffixes (`kg`, `%`, `USD`) or named Button/IconButton actions on
230
+ either side. Plain addons use subtle content color; actions retain their action
231
+ color. Include necessary units/format in the accessible label or description.
232
+ Prefixes are not masks and are not prepended to submitted values automatically;
233
+ normalization and complete phone/URL values belong to the host. Do not hide an
234
+ interactive addon from assistive technology. ButtonGroup supports text buttons,
235
+ IconButton or a mixture; every icon-only action needs its own accessible name.
236
+
237
+
238
+ ## Toggle and ToggleGroup
239
+
240
+ Toggle uses a native button with aria-pressed and type=button. Use defaultPressed
241
+ for local state or pressed/onPressedChange for controlled state. Keep its visible
242
+ or accessible name stable across changes. Native onClick runs first; calling
243
+ preventDefault cancels the state change. Disabled/loading prevent activation.
244
+ iconOnly wraps host artwork in IconSlot; supply aria-label for that action.
245
+ Sizes and variants follow Button. The ref targets HTMLButtonElement.
246
+
247
+ ToggleGroup takes a label and unique-value options with label, optional icon and
248
+ disabled. selectionMode is multiple by default; single permits at most one value
249
+ and allows clearing it. Both modes use string arrays: defaultValue for local
250
+ state, value/onValueChange for host state. Controlled values do not update unless
251
+ the host updates them. Keep the selection mode stable, or remount/reset state
252
+ when changing it. Missing-option values remain in host/local state; trim them in
253
+ the host if needed. Empty options render an empty named group.
254
+
255
+ Tab visits each enabled toggle. Orientation-aware arrows (including RTL), Home
256
+ and End move focus, skip disabled options and do not select. Enter/Space activate.
257
+ The group retains intrinsic action widths rather than breaking words or compressing
258
+ icons. In constrained layouts use an enclosing horizontal ScrollArea, icon-only
259
+ actions or vertical orientation. The ref targets HTMLDivElement. iconOnly requires icons and uses option labels as
260
+ accessible names. Text+icon and text-only options are supported too.
261
+
262
+ These are pressed action buttons, not tabs, radio inputs or switches. No hidden
263
+ inputs, automatic form submission or native form-reset behavior is added. For
264
+ form fields use RadioGroup/CheckboxGroup; for panels use Tabs.
265
+ Uncontrolled default props initialize state only. Both exports are available in 0.2.3.
266
+
267
+ ```tsx
268
+ <Toggle defaultPressed onPressedChange={active => console.log(active)}>Bold</Toggle>
269
+ <ToggleGroup label="Text formatting" defaultValue={["bold"]}
270
+ options={[{value: "bold", label: "Bold"}, {value: "italic", label: "Italic"}]} />
271
+ ```
272
+
273
+
274
+ ## SegmentedControl
275
+
276
+ SegmentedControl is a segmented presentation of Flood RadioGroup with native
277
+ radio inputs, a visible label/legend and shared inset surface. Supply label,
278
+ name and unique-value options (string label, optional icon/disabled). Use short
279
+ labels for a small set of modes. Default orientation is horizontal; vertical,
280
+ iconOnly and fullWidth are supported. iconOnly requires each option's icon and
281
+ keeps its label available to assistive technology. fullWidth distributes equal
282
+ width to horizontal segments. controlSize is sm/md/lg/xl; touch targets may grow
283
+ the shared shell. Intrinsic groups retain readable labels rather than compressing
284
+ icons; use a constrained ScrollArea or another orientation if needed.
285
+
286
+ value/onValueChange use a single string; defaultValue initializes local state.
287
+ An absent or unmatched initial value leaves the group unselected. Once selected,
288
+ clicking the current segment cannot clear it; controlled host state can clear it.
289
+ Keep names unique among independent groups in the same form. Native name, form,
290
+ required, disabled fieldsets and reset are preserved through RadioGroup. Reset
291
+ restores the latest defaultValue for local state, preserves controlled host
292
+ values, respects cancellation and does not call onValueChange. Empty/missing
293
+ options are not invented. No automatic fallback to the first option is applied.
294
+
295
+ Tab reaches the selected enabled radio (browser defaults apply if none is
296
+ selected). Arrows focus and select, wrapping and skipping disabled options;
297
+ horizontal left/right follow RTL. Home/End select the first/last enabled item.
298
+ Space selects without clearing. A canceled native onKeyDown skips Flood's
299
+ keyboard enhancement. Enter retains native radio/form behavior. The ref targets
300
+ HTMLFieldSetElement. Hint/message/error/status follow Fieldset.
301
+
302
+ Use SegmentedControl for one value/mode, ToggleGroup for optional pressed actions
303
+ and actual tabs for associated tab panels. This does not implement tab panels.
304
+ Available in 0.2.3. Use matching installed declarations.
305
+
306
+ ```tsx
307
+ <SegmentedControl label="Time range" name="period" defaultValue="day"
308
+ options={[{value: "day", label: "Day"}, {value: "week", label: "Week"},
309
+ {value: "month", label: "Month"}]} />
310
+ ```
311
+
312
+
313
+ ## visually hidden external labels
314
+
315
+ Field and Fieldset accept hideLabel (false by default). It visually hides the real
316
+ Label or legend, preserving its association and accessible name, while removing
317
+ the label's layout row and spacing while it has no focused descendants. The
318
+ required label/legend prop remains required.
319
+ RadioGroup, CheckboxGroup and SegmentedControl inherit hideLabel. Required native
320
+ controls, descriptions, error/status, group names and form behavior are unchanged.
321
+ Keep essential instructions in a visible hint/message when they are needed.
322
+ Prefer textual label content. Hidden labels/legends reveal while a descendant
323
+ has focus, then hide again when focus leaves. A supporting link in a Field label
324
+ or a control in a Fieldset first legend therefore retains visible keyboard focus;
325
+ React child state, refs and native first-legend disabled exceptions are preserved.
326
+ Do not nest another labelable control (button/input) inside a Field label.
327
+
328
+ For Input/Textarea/Select/InputGroup apply hideLabel to their surrounding Field,
329
+ not the native control. Input aria-label/aria-labelledby remain available when
330
+ the host deliberately provides its own labeling. Placeholder is not a label.
331
+ ButtonGroup/ToggleGroup already expose their group label as an accessible name;
332
+ this flag does not hide button text or the per-option labels of Checkbox/Radio/
333
+ Switch. Standalone icon actions still need an accessible name.
334
+
335
+ ```tsx
336
+ <Field label="Search projects" hideLabel>
337
+ {control => <Input {...control} type="search" />}
338
+ </Field>
339
+ <SegmentedControl label="Time range" hideLabel name="period" defaultValue="day"
340
+ options={[{value:"day",label:"Day"},{value:"week",label:"Week"}]} />
341
+ ```
342
+
343
+ This compatible prop addition is available in 0.2.3. It requires Flood CSS.
344
+
345
+
346
+ ## Collapsible and Accordion
347
+
348
+ Collapsible takes a plain-text title, React children and open/onOpenChange or
349
+ local defaultOpen. Default closed; native Enter/Space activate its named button.
350
+ The trigger owns aria-expanded and aria-controls with stable instance IDs. Its
351
+ optional headingLevel adds a native h2–h6 around the button. Disabled affects the
352
+ trigger only; content inside an open panel remains interactive. controlSize is
353
+ sm/md/lg/xl. Native div props, className/style and HTMLDivElement ref target the
354
+ root; title names the trigger rather than the root's HTML title attribute.
355
+
356
+ Accordion takes an accessible group label and unique-value items with title,
357
+ content and optional disabled. expansionMode is single by default (one or none
358
+ open), or multiple. Both use string-array value/onValueChange or defaultValue.
359
+ The default headingLevel is 3; choose 2–6 to match surrounding headings. Up/Down,
360
+ Home/End focus enabled top-level triggers without changing expansion. Tab reaches
361
+ triggers and open panel controls normally; Enter/Space open/close. Nested accordions
362
+ keep their own keyboard navigation. The ref targets the group div. There is no
363
+ visible group caption to hide; headings and trigger titles remain visible.
364
+
365
+ Defaults initialize local state only. Controlled state needs host updates; keep
366
+ expansionMode stable or reconcile/remount when switching. Duplicate item values
367
+ and multiple expanded values in single mode are rejected. Missing-item values
368
+ remain in state until the host removes them. Disabled does not force panels shut.
369
+
370
+ Both share the same original disclosure anatomy. Panels stay mounted with native
371
+ hidden when closed, preserving local content state while removing hidden content
372
+ from focus/navigation. Existing form values and constraints remain native even
373
+ when hidden; manage inactive form fields in the host. If programmatic closing
374
+ hides focused content, focus returns to the enabled trigger unless already moved
375
+ elsewhere. Hosts own focus if the trigger is disabled or its whole item is removed.
376
+ There is no built-in Escape-to-close, form reset or persistence. Panels change
377
+ visibility immediately; only the chevron animates, with reduced motion disabling
378
+ that transition. No extra landmark regions are created per panel.
379
+
380
+ ```tsx
381
+ <Collapsible title="Advanced settings"><Input aria-label="Project prefix" /></Collapsible>
382
+ <Accordion label="Questions" headingLevel={2} items={[
383
+ {value:"install",title:"How do I install Flood?",content:"Install the packages."},
384
+ ]} />
385
+ ```
386
+
387
+ Available in 0.2.3. Import Flood CSS and use FloodRoot for dark foundations.
388
+
389
+ Disclosure corner geometry: Accordion headers have square internal seams. Only
390
+ the first header has upper shell corners, and only a closed last header has lower
391
+ shell corners. An open Collapsible header has square lower corners where its
392
+ panel begins. Hover backgrounds follow that same shape without clipping focus.
393
+
394
+
395
+ ## Tooltip, Popover and Dialog
396
+
397
+ All three use a trigger render function: `trigger={props => <Button {...props}>Open</Button>}`.
398
+ Forward every supplied prop and ref to one actual Button/native button in the same
399
+ document. Keep it mounted and visible while its overlay is open. Compose any host
400
+ handlers with the supplied ones, rather than replacing them; merge existing
401
+ aria-describedby with Tooltip's supplied value if adding another description.
402
+ These client interactions require modern native Popover API / dialog showModal
403
+ support. No older-browser polyfill is bundled. Native top-layer placement keeps
404
+ the panel in its original DOM/theme scope and avoids overflow clipping.
405
+
406
+ Tooltip takes text-only content, side (top default), align (center default),
407
+ delay in milliseconds (300 default) and disabled. Keyboard focus shows immediately;
408
+ mouse/pen hover uses the delay. Moving onto the tooltip keeps it visible. Escape
409
+ dismisses until a new focus/hover interaction. It never moves focus; content is
410
+ associated via aria-describedby. disabled suppresses the tooltip, not its trigger
411
+ action. Touch hover is not relied on; essential instructions belong in visible
412
+ content. Use Popover for interactive content; tooltip text is not a button's name.
413
+
414
+ Popover takes a visible title, children, trigger, side (bottom), align (start),
415
+ open/onOpenChange or local defaultOpen. It is a nonmodal dialog: focus enters its
416
+ content, but Tab is not trapped. Escape, outside pointer and focus leaving request
417
+ closing. The upper-end close action is an IconButton with a decorative cross; closeLabel
418
+ configures its accessible name. initialFocusRef can
419
+ choose an enabled element inside the panel; otherwise content gets initial focus.
420
+ Disabled prevents trigger activation. The div ref/className/style/native props
421
+ target the actual panel. Side flips and coordinates clamp near viewport edges;
422
+ scroll, viewport resize and layout changes reposition it. Native hidePopover requests state changes and a controlled owner can refuse
423
+ closure. Engine owns top/left,
424
+ position and available-size variables. Logical start/end alignment follows RTL.
425
+
426
+ Dialog uses native showModal for backdrop, inert background and nested modal
427
+ behavior. It takes title, optional plain description, children, trigger and the
428
+ same controlled/local state, disabled trigger, closeLabel and initialFocusRef.
429
+ Its upper-end close action also uses IconButton.
430
+ Escape requests closure of the topmost layer. closeOnOutsideClick defaults true;
431
+ turning it off preserves Escape and the explicit close action. Tab/Shift+Tab cycle
432
+ inside the modal, including nested nonmodal content; background scrolling is
433
+ locked without changing layout width and restored when the last modal closes.
434
+ Root props/style and HTMLDialogElement ref target the actual dialog. Native
435
+ close/method=dialog requests state changes; a controlled owner can refuse them.
436
+
437
+ Popover/Dialog children mount while open and unmount when closed. Keep persistent
438
+ form data in the host. Defaults initialize once; controlled owners must update
439
+ open. On closing, focus returns to the connected enabled trigger when appropriate;
440
+ focus deliberately moved elsewhere is preserved for nonmodal dismissal. Hosts own
441
+ fallback focus if a trigger disappears or is disabled. Choose safe initial focus
442
+ for consequential actions. Complex dialog content need not be flattened into a
443
+ single description; use its visible structure. No menu/listbox semantics are
444
+ implied by Popover. No opening/closing animation is applied in this first slice.
445
+
446
+ ```tsx
447
+ <Tooltip content="Additional context" trigger={p => <Button {...p}>Help</Button>} />
448
+ <Popover title="Settings" trigger={p => <Button {...p}>Settings</Button>}>
449
+ <Input aria-label="Project name" />
450
+ </Popover>
451
+ <Dialog title="Create a project" trigger={p => <Button {...p}>Open dialog</Button>}>
452
+ <Field label="Project name">{p => <Input {...p} />}</Field>
453
+ </Dialog>
454
+ ```
455
+
456
+ Available in 0.2.3. Use Flood CSS and FloodRoot. These are original native
457
+ implementations, without a third-party component/positioning library.
458
+
459
+
460
+ ## Tabs, Breadcrumb and Pagination
461
+
462
+ Tabs connects unique-value items (label, content, optional icon/disabled) to a
463
+ named tablist and retained panels. Use defaultValue for local state or
464
+ value/onValueChange for controlled state. Missing, invalid or individually disabled
465
+ selection derives the first available item without a change callback. Reconcile
466
+ host values when changing items. Global disabled disables tab controls while
467
+ leaving the selected panel visible. iconOnly requires all icons and keeps labels
468
+ as accessible names. Native div props/ref target the root.
469
+
470
+ One enabled tab is in the Tab sequence. Orientation-aware arrows, Home/End move
471
+ focus, wrapping and skipping disabled tabs; horizontal start/end follows RTL.
472
+ activationMode is automatic (arrow navigation activates) or manual (Enter/Space
473
+ activate). Manual focus resets to active tab on leaving the list. Panels are
474
+ focusable, labelled by their tabs and retained with hidden when inactive, preserving
475
+ state and native form constraints. Hosts own inactive-field validation. External
476
+ selection changes return focus from a hidden panel to the selected tab if needed.
477
+ Horizontal overflow uses Flood ScrollArea; vertical layout wraps its tab labels.
478
+ Sizes are controlSize sm/md/lg/xl. There is no tab deletion/loading/fetch model.
479
+ The dark recipe is one contained surface: the active tab shares the panel's
480
+ background, while inactive tabs sit on the recessed navigation strip. Both
481
+ orientations share a single outer perimeter, without a separate panel border or
482
+ selection underline. Keep panel content free of redundant enclosing surfaces.
483
+
484
+ Breadcrumb uses an accessible nav label (Breadcrumb by default), an ordered items
485
+ array of label/optional href, and a decorative separator (/ by default). The last
486
+ item always has aria-current=page, as a native link or text. Parent links preserve
487
+ browser navigation; host routes and client interception remain host-owned. It
488
+ wraps and supports long labels. Native nav props/ref target HTMLElement.
489
+
490
+ Pagination is controlled: page/pageCount are required positive safe integers,
491
+ 1 <= page <= pageCount; omit the component for empty datasets. Host must reconcile
492
+ page when count changes. onPageChange requests local updates; getHref renders
493
+ native route links instead of buttons. In link mode onPageChange is notification,
494
+ not navigation cancellation; modifier clicks retain native behavior. Host routing
495
+ can intercept the bubbling nav click. Current page uses aria-current, and boundary
496
+ Previous/Next controls have no active href or disabled native buttons. disabled
497
+ makes every control noninteractive. No callback fires for the current page.
498
+
499
+ siblingCount 0/1/2 gives at most 5/7/9 numeric/ellipsis slots plus Previous/Next,
500
+ independent of pageCount magnitude. Ellipses are decorative. controlSize matches
501
+ field sizes; wrapping prevents narrow-width overflow and touch controls retain
502
+ 44px targets. label, previousLabel, nextLabel and pageLabel localize naming.
503
+ The native nav ref is HTMLElement. This component does not fetch data or own URLs.
504
+
505
+ ```tsx
506
+ <Tabs label="Project sections" items={[
507
+ {value:"overview",label:"Overview",content:"Project context."},
508
+ {value:"activity",label:"Activity",content:"Recent updates."},
509
+ ]} />
510
+ <Breadcrumb items={[{label:"Projects",href:"/projects"},{label:"Flood"}]} />
511
+ <Pagination page={1} pageCount={20} getHref={page => `?page=${page}`} />
512
+ ```
513
+
514
+ All three are available in 0.2.3. Use Flood styles and FloodRoot for the neutral dark profile.
515
+
516
+
517
+ ## Badge, Spinner, Skeleton and Avatar · 0.2.4
518
+
519
+ These original primitives are compatible additions in the 0.2.4.
520
+ Import Flood CSS and use FloodRoot. Verify registry publication before installing.
521
+ Native span props and refs target their roots; className/style remain host overrides.
522
+
523
+ Badge is a noninteractive span: size sm/md/lg, tone neutral/accent/error/warning/
524
+ success/info and variant subtle/outline. Its text carries the meaning; color is
525
+ supplementary. It has no selection, dismissal, click-action or live-region model.
526
+ Use a separate named action when needed instead of treating it as a button.
527
+
528
+ Spinner defaults to size md and label="Loading". Localize label for standalone
529
+ status. decorative hides it and its label from assistive technology. Button
530
+ loading uses the same Spinner decoratively, inherits its icon size and retains
531
+ its accessible button name and aria-busy. Announcements belong at the appropriate
532
+ host level; do not create one live region for each decorative loading graphic.
533
+ Reduced motion stops rotation. This indicator does not measure completion.
534
+
535
+ Skeleton is always aria-hidden. shape is text/rounded/circle; width and height
536
+ accept CSS dimensions (numbers are React CSS pixels), and style overrides them.
537
+ Prefer relative dimensions. For a circle supply equal dimensions. animate defaults
538
+ true; reduced motion and forced colors stop animation. Match the final geometry;
539
+ Skeleton does not fetch, detect loading or announce changes. The host owns busy
540
+ state, loading text and replacement with real content. Do not add interactive children.
541
+
542
+ Avatar requires name, accepts src, fallback text, size sm/md/lg, shape circle/
543
+ rounded and decorative. The root exposes one named image; the internal image has
544
+ empty alt and is hidden from assistive technology. Use decorative beside an
545
+ already visible equivalent name. Defaults use the first letters of the first two
546
+ whitespace-separated name parts; override fallback for your locale or naming rules.
547
+ The image overlays the fallback only after successful load; an error retains the
548
+ fallback. A new src remounts the image load state, including returning to an earlier
549
+ source. The root stays the same size. This API does not upload or crop images.
550
+
551
+ ```tsx
552
+ <Badge tone="success">Active</Badge>
553
+ <Spinner label="Loading workspace" />
554
+ <Skeleton width="12rem" />
555
+ <Avatar name="Alex Morgan" src="/team/alex.webp" />
556
+ ```
557
+
558
+
559
+ ## Progress, Meter and Alert · 0.2.4
560
+
561
+ Progress describes task completion. label is required; max defaults to 100 and
562
+ value is a finite number in 0..max. Omit value for indeterminate progress; its
563
+ progressbar then omits aria-valuenow. Meter describes a measurement in a bounded
564
+ range, such as storage or battery. It requires value, with min=0 and max=100 by
565
+ default. Require finite min < max, a finite range span, and min <= value <= max.
566
+ Invalid ranges/values throw RangeError; they are not silently clamped.
567
+
568
+ Both support size sm/md/lg, semantic tone, hideLabel and valueText. Tone defaults
569
+ to accent for Progress and neutral for Meter. valueText supplies localized,
570
+ human-readable units; it also appears beside the caption. hideLabel visually
571
+ hides the caption while retaining the accessible name and valueText. The bar's
572
+ role is progressbar or meter; neither becomes a live region. Native div props,
573
+ className/style and HTMLDivElement ref target the wrapper; the internal bar owns
574
+ its ARIA range and label associations. Wrapper ARIA props stay on the wrapper; use valueText for range-specific wording. No keyboard model,
575
+ threshold calculation, fetching, timers or task cancellation is built in.
576
+ Reduced motion stops indeterminate movement and value transitions. Forced colors
577
+ preserves the track boundary and fill. Use a host loading message when needed.
578
+
579
+ Alert is an inline message, with optional plain-text title and React children.
580
+ Tones are neutral/info/success/warning/error; info is the default. Meaning must
581
+ remain in the text. Original decorative status glyphs are included; icon accepts
582
+ noninteractive host artwork, and icon={null} removes it without an empty column.
583
+ The title is message content, not the root's HTML title tooltip or a document heading.
584
+ Native div props/className/style/ref target the root.
585
+
586
+ announcement defaults to off for static messages. polite uses role=status;
587
+ assertive uses role=alert. Choose urgency deliberately for dynamically inserted
588
+ content. Announcements depend on actual assistive technology; initial static
589
+ markup is not guaranteed to be read aloud. No focus movement, auto-dismiss,
590
+ timeout or built-in close/action button is supplied. Compose named host actions
591
+ inside children when needed. Alert is separate from Dialog and future Toast.
592
+
593
+ See the [WAI Meter pattern](https://www.w3.org/WAI/ARIA/apg/patterns/meter/),
594
+ [Alert pattern](https://www.w3.org/WAI/ARIA/apg/patterns/alert/) and
595
+ [progressbar definition](https://www.w3.org/TR/wai-aria-1.2/#progressbar).
596
+
597
+ ```tsx
598
+ <Progress label="Uploading files" value={60} valueText="60%" />
599
+ <Progress label="Preparing workspace" />
600
+ <Meter label="Storage used" min={0} max={100} value={75} valueText="75 of 100 GB" />
601
+ <Alert tone="warning" title="Storage is almost full">
602
+ Free some space before your next upload.
603
+ </Alert>
604
+ ```
605
+
606
+
607
+ ## DescriptionList and Table · 0.2.4
608
+
609
+ DescriptionList renders native dl groups, each with dt and dd. items is a readonly
610
+ array of {id, term, description}; IDs must be unique and nonempty, and are React
611
+ keys rather than DOM anchors. term is text and description is ReactNode, so a
612
+ value can include a Badge, Link or other meaningful content. The host formats
613
+ values and supplies fallback text for missing data. An empty items array stays
614
+ an empty list, without invented placeholder content. Native dl props,
615
+ className/style and HTMLDListElement ref target the list.
616
+
617
+ layout defaults inline: term and value share a row. It becomes stacked below a
618
+ 22rem component container width, independently of viewport width. layout=stacked
619
+ always puts the value below its term. Density defaults compact; comfortable adds
620
+ space between pairs, without inflating the term/value relationship. dividers is
621
+ false by default and adds quiet separators when enabled. This is not an editable
622
+ form and its terms do not label controls; use Field/Fieldset for form inputs.
623
+
624
+ Table wraps an actual HTML table in Flood's horizontal ScrollArea. caption is
625
+ required and must be meaningful; hideCaption visually hides it without removing
626
+ the accessible table name. children owns native thead/tbody/tfoot/colgroup markup
627
+ and th/td associations. Supply scope=col or scope=row for simple headers, and
628
+ explicit id/headers associations for complex spanning headers. Do not add another
629
+ caption child. See [WAI table guidance](https://www.w3.org/WAI/tutorials/tables/).
630
+
631
+ Table density defaults comfortable; compact reduces vertical cell padding.
632
+ striped is false by default. Native tfoot rows use a quiet tinted background and
633
+ medium-weight text to distinguish summaries; the top separator stays decorative.
634
+ data-numeric on a th or td uses logical end alignment,
635
+ tabular figures and no wrapping for numbers. data-align=start/center/end overrides
636
+ alignment explicitly. Text cells wrap; set style.minWidth to preserve room for a
637
+ wide table, and the containing ScrollArea handles overflow. The host owns dates,
638
+ units, totals, empty rows/colSpan and any cell actions. Avoid interactive whole rows.
639
+
640
+ Native table props, className/style and HTMLTableElement ref target the table,
641
+ not its viewport. dir/lang/hidden also govern the containing scroll area.
642
+ scrollLabel names that viewport independently and defaults to caption. The native
643
+ table keeps its own caption; viewport keyboard scrolling does not turn it into a
644
+ grid widget. Keep action controls inside cells normally focusable. No sorting,
645
+ selection, data fetching, pagination, sticky header or virtualization is implied.
646
+ This is the compositional table primitive, not a DataTable engine.
647
+
648
+ ```tsx
649
+ <DescriptionList items={[
650
+ {id:"project",term:"Project",description:"Flood"},
651
+ {id:"version",term:"Version",description:"0.2.4"},
652
+ ]} />
653
+ <Table caption="Workspace projects" style={{minWidth:"32rem"}}>
654
+ <thead><tr><th scope="col">Project</th><th scope="col" data-numeric>Members</th></tr></thead>
655
+ <tbody><tr><th scope="row">Flood</th><td data-numeric>12</td></tr></tbody>
656
+ </Table>
657
+ ```
658
+
659
+
660
+ ## Menu · 0.2.4
661
+
662
+ Menu is an original action menu using Flood's positioning/layer stack and the
663
+ native Popover API. label names the menu; there is no extra visible caption.
664
+ trigger is a render function receiving OverlayTriggerProps: forward all props
665
+ and ref to one persistent Button/IconButton/native button, and compose existing
666
+ handlers rather than replacing the supplied ones. The trigger exposes
667
+ aria-haspopup=menu, aria-expanded and aria-controls. disabled affects the trigger,
668
+ not actions inside a menu the controlled host keeps open.
669
+
670
+ items is a readonly array of action items {id,label,icon?,shortcut?,disabled?,tone?}
671
+ or separators {id,type:"separator"}. IDs must be unique and nonempty, action
672
+ labels nonempty, and at least one action must exist. The optional item type is
673
+ item. Icons must be decorative, noninteractive artwork. shortcut is display-only,
674
+ not an installed keyboard binding or part of the accessible action name. Destructive
675
+ actions use tone=danger; that visual treatment does not perform confirmation or
676
+ execute any business operation. Disabled items remain focusable with aria-disabled,
677
+ but neither pointer nor keyboard activation calls onAction or closes the menu.
678
+
679
+ Enter/Space/ArrowDown open at the first action; ArrowUp opens at the last.
680
+ Inside: Up/Down wrap, Home/End choose edges, printable label-prefix typeahead
681
+ matches items (repeated characters cycle). Separators are not focusable. Enter
682
+ or Space activates through a native button. Escape closes the top menu and
683
+ returns focus. Tab moves to the next focusable control after the trigger;
684
+ Shift+Tab returns to the trigger. The menu does not trap focus. Outside pointer
685
+ or focus departure requests dismissal and preserves deliberately moved focus.
686
+
687
+ open/onOpenChange is controlled, or use defaultOpen for initial local state.
688
+ The host may refuse a close request. onAction(id) requests closing before calling
689
+ the host; the host owns business logic, asynchronous work and errors. If it moves
690
+ focus elsewhere or opens a Dialog, normal focus restoration must not steal it.
691
+ Actions mount while open and unmount on close. The persistent popup ref and native
692
+ div props/className/style target its panel. Engine-owned coordinates and available
693
+ space variables belong to positioning; side/align follow Popover conventions.
694
+ controlSize is sm/md/lg/xl (sm default); pointer-coarse targets retain touch size.
695
+
696
+ The popup uses Flood ScrollArea for long action lists, bounded to the available
697
+ viewport. Its inner corners follow shell padding and border geometry. Focus is
698
+ inset to stay visible. There is no href/navigation-item, submenu, checkbox/radio,
699
+ context-menu trigger or menubar contract in this first action-menu component.
700
+ Use ordinary Link for ordinary navigation; keep different selection models separate.
701
+
702
+ See [WAI Menu Button](https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/) and
703
+ [Menu keyboard patterns](https://www.w3.org/WAI/ARIA/apg/patterns/menubar/).
704
+
705
+ ```tsx
706
+ <Menu label="Project actions"
707
+ trigger={props => <Button {...props}>Project actions</Button>}
708
+ items={[{id:"rename",label:"Rename…"},{id:"divider",type:"separator"},
709
+ {id:"delete",label:"Delete project",tone:"danger"}]}
710
+ onAction={id => chooseProjectAction(id)}
711
+ />
712
+ ```
713
+
714
+ ### Toolbar and Kbd (0.2.4)
715
+
716
+ Toolbar groups Button, IconButton, Toggle, native buttons/links and Menu triggers
717
+ under one named action panel. After hydration it uses one Tab entry, directional
718
+ arrows, Home/End and optional looping. Disabled/hidden actions are skipped. Arrows
719
+ move focus, not activation. Horizontal order follows writing direction. In vertical
720
+ toolbars open Menu with Enter/Space; horizontal menus retain Up/Down opening.
721
+ Keep text inputs and independent selection widgets outside this action-only API.
722
+ Native child handlers and refs remain attached; Toolbar owns their tabIndex while
723
+ mounted and restores original attributes on cleanup. Without JavaScript, native
724
+ child Tab order remains available. Use a meaningful required label.
725
+
726
+ Kbd renders native kbd markup with sm/md/lg sizes and relative keycap heights.
727
+ It displays a key or combination; the host implements real shortcuts. It is not
728
+ interactive. Menu shortcut hints reuse Kbd and remain hidden from item names.
729
+
730
+ ```tsx
731
+ <Toolbar label="Editor actions">
732
+ <Button variant="ghost">Undo</Button>
733
+ <Toggle variant="ghost">Bold</Toggle>
734
+ <Button variant="ghost">Share</Button>
735
+ </Toolbar>
736
+ <Kbd>Ctrl</Kbd> + <Kbd>K</Kbd>
737
+ ```
738
+
739
+ ### Card (0.2.4)
740
+
741
+ Card composes Surface into a header, body and footer. Optional title, description,
742
+ actions and footer accept React content; children are the body. Missing slots
743
+ create no empty section. Use Surface when only a background/padding is needed.
744
+
745
+ Card is a noninteractive div. Put Button, Link or Menu in actions/footer and keep
746
+ its container out of the Tab order. Do not make the entire card clickable around
747
+ nested controls. Native div props, styles and HTMLDivElement ref target the root.
748
+ The title prop is heading content, not an HTML tooltip; supply phrasing content.
749
+ headingLevel defaults to 2; choose the correct hierarchy for the host page.
750
+
751
+ Compact/comfortable density controls existing spacing tokens. Header actions wrap
752
+ without clipping menus or focus indicators. Footer uses a quiet separator; a
753
+ footer-only card has no redundant separator. Elevation is default or raised.
754
+ No data access, selection or interactive-card behavior is implied.
755
+
756
+ ```tsx
757
+ <Card title="Project details" description="Keep your team in sync."
758
+ footer={<Button variant="subtle">Open project</Button>}>
759
+ <Text as="p" size="compact">A calm place for the next idea.</Text>
760
+ </Card>
761
+ ```
762
+
763
+ ## Structural, media and accessible primitives · 0.2.4
764
+
765
+ Container centers a max-width box with logical gutters. size sm/md/lg uses
766
+ 40/64/80rem bounds including padding; full removes the max. gutter uses Space.
767
+ Grid defaults to auto-fit with a16rem semantic minimum clamped to available
768
+ width. gap uses Space. minItemWidth overrides the auto minimum; explicit columns
769
+ (1/2/3/4/6/12) remain fixed. Grid is ordinary layout, never an ARIA grid widget.
770
+
771
+ VisuallyHidden preserves content in the accessibility tree using span/div. Default
772
+ is for noninteractive content. Use focusable for a skip link: focus-within reveals
773
+ its wrapper; style the actual link's focus. This is different from aria-hidden or
774
+ conditional rendering. Native captions/legends may use the same hiding utility
775
+ without an invalid generic wrapper. Spinner and hidden indicator captions compose
776
+ VisuallyHidden.
777
+
778
+ AspectRatio reserves responsive media geometry before loading, accepting a finite
779
+ positive ratio (16/9 default). Direct img/video/iframe children fill its box. It
780
+ does not clip focus or make arbitrary long content fit; the host owns alternative
781
+ text, captions, media controls and deliberate cropping. Image is a native img
782
+ with required alt (empty for decorative content), cover/contain fit and preserved
783
+ srcSet/sizes/loading/decoding/dimensions/events/ref. No optimizer, fetch engine or
784
+ fallback is implied. Avatar composes Image for its own loading lifecycle. Supply
785
+ image dimensions or AspectRatio to reserve space.
786
+
787
+ List renders native ul/ol with native li children and token gap. ordered selects
788
+ ol; start/reversed apply there. start must be a safe integer. marker=false retains
789
+ explicit list semantics for platforms that drop lists without visible markers.
790
+ This is content, not ListBox selection or arrow navigation.
791
+
792
+ EmptyState composes Stack, Heading, Text, IconSlot and Inline. Required title,
793
+ optional description, decorative icon and actions explain absent content; choose
794
+ headingLevel for the host hierarchy. It is static, with no automatic focus or
795
+ announcement. compact/comfortable density controls padding. The host implements
796
+ real actions and decides whether asynchronously changed results need announcing.
797
+
798
+ Checkbox adds indeterminate for a host-controlled mixed presentation after
799
+ hydration. checked independently determines form submission; mixed does not mean
800
+ a third submitted value. The host owns aggregate/select-all values. Native reset
801
+ and ref access remain available. Radio and Switch do not expose indeterminate.
802
+
803
+ ButtonGroup/ToggleGroup retain their keyboard contracts and locally scroll when
804
+ a connected row cannot fit. InputGroup actions keep words intact and can wrap to
805
+ a new row while the input retains usable space. Plain string adornments are bidi
806
+ isolated; for protocols/phones with explicit direction pass a bdi dir=ltr node.
807
+ Keep very long addon content concise. Hidden native choices hide their label too.
808
+
809
+ Dark foundations bundle both normal and italic Inter, including Cyrillic, with
810
+ original OFL notices. Color alone never communicates validation or progress.
811
+
812
+ CodeSnippet codeDirection defaults to ltr independently of RTL parent action layout; choose rtl/auto explicitly for content that needs it. Native dir still applies to the wrapper. Protected ScrollArea reveals focused descendants fully when needed, composes the host focus handler and avoids scrolling to nested top-layer controls.
813
+
814
+ Checkbox, Radio and Switch apply native hidden/dir/lang to their visible label wrapper as well as the input, so a component-local language/direction applies to its whole choice.