@abuzareal/contour-ui 0.4.0 → 0.6.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 (186) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/COMBOBOX.md +87 -0
  3. package/CONTRACTS.md +23 -17
  4. package/DOCS.md +80 -0
  5. package/FOUNDATION.md +35 -0
  6. package/GETTING-STARTED.md +63 -0
  7. package/NEXTJS.md +5 -1
  8. package/OVERLAYS.md +52 -0
  9. package/PUBLIC-API.md +189 -162
  10. package/README.md +44 -28
  11. package/RECIPES.md +43 -0
  12. package/RELEASING.md +2 -2
  13. package/SIZE.md +51 -0
  14. package/STYLING.md +68 -0
  15. package/dist/actions.css +7 -0
  16. package/dist/components/hero/Sculpture.js +2 -1
  17. package/dist/components/three/ObjectStudy.js +2 -1
  18. package/dist/components/ui/Alert.d.ts +2 -1
  19. package/dist/components/ui/Alert.js +3 -3
  20. package/dist/components/ui/Avatar.d.ts +3 -1
  21. package/dist/components/ui/Avatar.js +3 -3
  22. package/dist/components/ui/Badge.js +2 -2
  23. package/dist/components/ui/BrandMark.js +2 -1
  24. package/dist/components/ui/Breadcrumbs.d.ts +2 -1
  25. package/dist/components/ui/Breadcrumbs.js +3 -2
  26. package/dist/components/ui/Button.d.ts +2 -0
  27. package/dist/components/ui/Button.js +3 -2
  28. package/dist/components/ui/Card.js +2 -2
  29. package/dist/components/ui/Checkbox.js +3 -3
  30. package/dist/components/ui/Combobox.d.ts +325 -0
  31. package/dist/components/ui/Combobox.js +120 -0
  32. package/dist/components/ui/Combobox.types.d.ts +46 -0
  33. package/dist/components/ui/Combobox.types.js +1 -0
  34. package/dist/components/ui/CountUp.d.ts +2 -1
  35. package/dist/components/ui/CountUp.js +6 -3
  36. package/dist/components/ui/DataTable.d.ts +1 -0
  37. package/dist/components/ui/DataTable.js +15 -10
  38. package/dist/components/ui/DepthButton.js +2 -2
  39. package/dist/components/ui/Dialog.d.ts +5 -1
  40. package/dist/components/ui/Dialog.js +49 -18
  41. package/dist/components/ui/Disclosure.js +2 -1
  42. package/dist/components/ui/Divider.js +3 -2
  43. package/dist/components/ui/DropdownMenu.d.ts +11 -4
  44. package/dist/components/ui/DropdownMenu.js +109 -66
  45. package/dist/components/ui/EmptyState.js +2 -1
  46. package/dist/components/ui/Field.js +2 -2
  47. package/dist/components/ui/FlipCard.js +2 -2
  48. package/dist/components/ui/FloatingPortal.d.ts +10 -0
  49. package/dist/components/ui/FloatingPortal.js +13 -0
  50. package/dist/components/ui/FloatingScope.d.ts +9 -0
  51. package/dist/components/ui/FloatingScope.js +43 -0
  52. package/dist/components/ui/IconButton.js +2 -2
  53. package/dist/components/ui/Kbd.js +2 -1
  54. package/dist/components/ui/Magnetic.js +2 -1
  55. package/dist/components/ui/Marquee.js +3 -2
  56. package/dist/components/ui/OverlayTrigger.d.ts +11 -0
  57. package/dist/components/ui/OverlayTrigger.js +5 -0
  58. package/dist/components/ui/Pagination.d.ts +5 -1
  59. package/dist/components/ui/Pagination.js +3 -2
  60. package/dist/components/ui/Popover.d.ts +18 -8
  61. package/dist/components/ui/Popover.js +58 -17
  62. package/dist/components/ui/ProgressBar.js +2 -2
  63. package/dist/components/ui/RadioGroup.js +5 -5
  64. package/dist/components/ui/Reveal.d.ts +0 -1
  65. package/dist/components/ui/Reveal.js +4 -3
  66. package/dist/components/ui/RevealHeading.js +2 -1
  67. package/dist/components/ui/ScrollProgress.js +2 -1
  68. package/dist/components/ui/SectionLabel.js +2 -1
  69. package/dist/components/ui/SegmentedControl.js +6 -3
  70. package/dist/components/ui/SelectField.js +3 -2
  71. package/dist/components/ui/Skeleton.js +2 -2
  72. package/dist/components/ui/Slider.js +3 -3
  73. package/dist/components/ui/Spinner.js +2 -2
  74. package/dist/components/ui/Stat.js +2 -1
  75. package/dist/components/ui/Stepper.js +3 -3
  76. package/dist/components/ui/Switch.d.ts +2 -0
  77. package/dist/components/ui/Switch.js +6 -7
  78. package/dist/components/ui/Table.d.ts +20 -0
  79. package/dist/components/ui/Table.js +9 -0
  80. package/dist/components/ui/Tabs.js +7 -5
  81. package/dist/components/ui/Tag.d.ts +2 -1
  82. package/dist/components/ui/Tag.js +4 -3
  83. package/dist/components/ui/TextArea.js +2 -1
  84. package/dist/components/ui/TextField.js +3 -2
  85. package/dist/components/ui/ThemeScope.d.ts +13 -0
  86. package/dist/components/ui/ThemeScope.js +10 -0
  87. package/dist/components/ui/ThemeToggle.d.ts +3 -0
  88. package/dist/components/ui/ThemeToggle.js +4 -3
  89. package/dist/components/ui/TiltCard.js +3 -2
  90. package/dist/components/ui/Timeline.js +2 -2
  91. package/dist/components/ui/ToastProvider.d.ts +5 -2
  92. package/dist/components/ui/ToastProvider.js +5 -5
  93. package/dist/components/ui/Tooltip.d.ts +4 -4
  94. package/dist/components/ui/Tooltip.js +70 -10
  95. package/dist/data-display.css +5 -0
  96. package/dist/effects.css +6 -0
  97. package/dist/feedback.css +5 -0
  98. package/dist/fonts-latin.css +10 -4
  99. package/dist/fonts.css +8 -0
  100. package/dist/forms.css +5 -0
  101. package/dist/hooks/useComboboxAnchor.d.ts +17 -0
  102. package/dist/hooks/useComboboxAnchor.js +61 -0
  103. package/dist/hooks/useDismiss.d.ts +12 -3
  104. package/dist/hooks/useDismiss.js +5 -21
  105. package/dist/hooks/useFloatingPanel.d.ts +14 -0
  106. package/dist/hooks/useFloatingPanel.js +157 -0
  107. package/dist/hooks/useLayerDismiss.d.ts +4 -0
  108. package/dist/hooks/useLayerDismiss.js +48 -0
  109. package/dist/hooks/useMobileMenu.d.ts +0 -1
  110. package/dist/hooks/useMobileMenu.js +11 -16
  111. package/dist/hooks/usePanelFocus.d.ts +10 -0
  112. package/dist/hooks/usePanelFocus.js +60 -0
  113. package/dist/hooks/useTheme.d.ts +1 -0
  114. package/dist/hooks/useTheme.js +18 -5
  115. package/dist/index.d.ts +7 -0
  116. package/dist/index.js +3 -0
  117. package/dist/kit/data-display.css +6 -0
  118. package/dist/kit/forms.css +45 -2
  119. package/dist/kit/navigation.css +4 -0
  120. package/dist/kit/overlays.css +8 -4
  121. package/dist/lib/classNames.d.ts +2 -0
  122. package/dist/lib/classNames.js +16 -1
  123. package/dist/lib/componentClasses.d.ts +1 -0
  124. package/dist/lib/componentClasses.js +2 -0
  125. package/dist/lib/keyboard.d.ts +1 -1
  126. package/dist/lib/keyboard.js +5 -2
  127. package/dist/lib/layers.d.ts +18 -0
  128. package/dist/lib/layers.js +128 -0
  129. package/dist/lib/themeScope.d.ts +5 -0
  130. package/dist/lib/themeScope.js +3 -0
  131. package/dist/minimal-reset.css +6 -0
  132. package/dist/navigation.css +5 -0
  133. package/dist/overlays.css +5 -0
  134. package/dist/scoped/base.css +183 -0
  135. package/dist/scoped/kit/actions.css +209 -0
  136. package/dist/scoped/kit/data-display.css +498 -0
  137. package/dist/scoped/kit/effects.css +140 -0
  138. package/dist/scoped/kit/feedback.css +309 -0
  139. package/dist/scoped/kit/forms.css +404 -0
  140. package/dist/scoped/kit/navigation.css +266 -0
  141. package/dist/scoped/kit/overlays.css +299 -0
  142. package/dist/scoped/marquee.css +58 -0
  143. package/dist/scoped/spinner.css +20 -0
  144. package/dist/scoped/theme-toggle.css +38 -0
  145. package/dist/scoped/tokens.css +119 -0
  146. package/dist/scoped/ui.css +127 -0
  147. package/dist/scoped/utilities.css +17 -0
  148. package/dist/scoped-tokens.css +1 -0
  149. package/dist/scoped.css +12 -0
  150. package/dist/theme-toggle.css +2 -2
  151. package/dist/tokens.json +710 -0
  152. package/examples/optional/LazyScene.tsx +78 -0
  153. package/examples/optional/OptionalSmoothScroll.tsx +25 -0
  154. package/examples/optional/SmoothScrollOwner.tsx +7 -0
  155. package/examples/optional/optional.css +25 -0
  156. package/examples/recipes/AsyncProjectPicker.tsx +82 -0
  157. package/examples/recipes/DeleteProjectConfirmation.tsx +97 -0
  158. package/examples/recipes/InputCompositions.tsx +49 -0
  159. package/examples/recipes/RemoteRecordsTable.tsx +256 -0
  160. package/examples/recipes/useRemoteResource.ts +81 -0
  161. package/examples/reproduction/README.md +9 -0
  162. package/examples/reproduction/index.html +14 -0
  163. package/examples/reproduction/package.json +27 -0
  164. package/examples/reproduction/src/main.tsx +33 -0
  165. package/examples/reproduction/src/style.css +19 -0
  166. package/examples/reproduction/tsconfig.json +18 -0
  167. package/examples/reproduction/vite.config.ts +19 -0
  168. package/examples/starters/next/README.md +18 -0
  169. package/examples/starters/next/app/Demo.tsx +65 -0
  170. package/examples/starters/next/app/layout.tsx +25 -0
  171. package/examples/starters/next/app/next-page/page.tsx +9 -0
  172. package/examples/starters/next/app/page.tsx +11 -0
  173. package/examples/starters/next/app/style.css +19 -0
  174. package/examples/starters/next/next-env.d.ts +2 -0
  175. package/examples/starters/next/next.config.mjs +1 -0
  176. package/examples/starters/next/package.json +27 -0
  177. package/examples/starters/next/proxy.ts +16 -0
  178. package/examples/starters/next/tsconfig.json +30 -0
  179. package/examples/starters/vite/README.md +16 -0
  180. package/examples/starters/vite/index.html +14 -0
  181. package/examples/starters/vite/package.json +27 -0
  182. package/examples/starters/vite/src/main.tsx +81 -0
  183. package/examples/starters/vite/src/style.css +19 -0
  184. package/examples/starters/vite/tsconfig.json +18 -0
  185. package/examples/starters/vite/vite.config.ts +19 -0
  186. package/package.json +41 -5
package/CHANGELOG.md CHANGED
@@ -6,6 +6,30 @@ Changes to the published `@abuzareal/contour-ui` package. Version headings refer
6
6
 
7
7
  No changes recorded.
8
8
 
9
+ ## 0.6.0
10
+
11
+ Add allowed-value selection and public adoption material. This heading records source work, not npm publication.
12
+
13
+ - Add single-select Combobox backed by pinned Base UI 1.9.0, with stable string IDs, controlled/default selection and query, visible input refs/native form association, missing-value validation, translated status strings, loading/error/retry and Contour theme/layer/native-dialog integration. Existing native controls and overlay APIs remain compatible; Base UI adds installed dependency cost that is disclosed and measured.
14
+ - Add semantic Table with caption, native table ref/props and scroll-region part access. DataTable reuses the presentation frame while retaining its existing scalar/local-sort contract. TanStack Table 9.3.1/Store 0.11.2 stay application/development dependencies for a copyable controlled remote recipe.
15
+ - Publish executable async search, destructive Dialog confirmation and input composition recipes with cancellation/stale-response guards and truthful failures. Record explicit NumberField, adornment, password and Text/Heading scope decisions rather than speculative new exports.
16
+ - Ship independent pinned Vite/Next starters, an existing-app path, providers/forms/navigation/optional-feature guidance, script hash/nonce CSP integration and a public minimal reproduction. Add documentation coverage/issue links, copied-source checks and candidate-versus-registry starter verification.
17
+ - Keep existing production size budgets; introduce separate measured Combobox journeys. Expand same-archive React 18/19, package/declaration, browser, starter and CI gates. Manual screen-reader/physical device evidence and private catalogue changes remain separate.
18
+
19
+ See COMBOBOX.md, RECIPES.md, GETTING-STARTED.md, FOUNDATION.md and DOCS.md. This is an additive 0.x release, not a declaration of 1.0 readiness.
20
+
21
+ ## 0.5.0
22
+
23
+ Combined overlay, styling and size work. This heading records source work, not npm publication.
24
+
25
+ - Shared owner-document topmost dismissal and matching paint order for nested/sibling/portaled layers; cancellation, outside focus, ancestor visibility, native backdrop priority and cleanup are covered by regressions. Mobile-menu dismissal cooperates with the same registry.
26
+ - Popover/Menu/Tooltip gain collision-aware placement, bounded scrolling, RTL and CSS-zoom handling via Floating UI DOM 1.8.0. Default body/native-modal portals escape clipping and carry theme values/direction. Default DOM placement changes ancestor selectors; use direct part hooks or portal=false for a deliberate local integration. Public refs/state callbacks and void useDismiss remain compatible.
27
+ - Hoverable/persistent supplemental tooltips retain descriptions/names and Escape suppression. Public button renderTrigger and focus policies avoid nested controls; native Dialog preserves focus/scroll/lifecycle behavior and accepts explicit initial focus.
28
+ - Add isolated scoped.css, prefixed classes/tokens, controlled ThemeScope, minimal-reset.css, token JSON generation, forced-color states, logical RTL layouts and configurable internal labels/formatters. Legacy globals, branded fonts and application-layout compatibility remain; no consumer migration is performed. Compact density is deferred pending a concrete need.
29
+ - Add measured Vite/Next Button/settings/table fixtures, selected-icon comparison, actual network/install/archive categories, selective CSS entries and reviewed regression budgets. System fonts make no font requests; Fontsource/Lucide install costs are retained and disclosed. Three.js/Lenis remain optional and absent from core fixtures.
30
+
31
+ See OVERLAYS.md, STYLING.md and SIZE.md for migration, behavior, measurements and evidence boundaries. Independent applications/site versions are not upgraded by this package release.
32
+
9
33
  ## 0.4.0
10
34
 
11
35
  Native forms and tested application recipes. This heading records source work, not npm publication.
package/COMBOBOX.md ADDED
@@ -0,0 +1,87 @@
1
+ # Combobox — 0.6.0
2
+
3
+ Search a list and choose **one allowed value**. Use native SelectField when its simpler platform interaction is sufficient. Combobox is not free-text autocomplete, global search, a command menu, a multi-select, or a virtualized picker. Selection is a string ID or null; query text is never a submitted selection. It needs no provider or consumer-installed foundation package. [Foundation decision](FOUNDATION.md).
4
+
5
+ ```tsx
6
+ import { useRef } from "react";
7
+ import { Button, Combobox } from "@abuzareal/contour-ui";
8
+ import "@abuzareal/contour-ui/scoped.css";
9
+ const projects = [
10
+ { value: "atlas", label: "Atlas" },
11
+ { value: "notes", label: "Field notes" },
12
+ { value: "closed", label: "Closed project", disabled: true },
13
+ ];
14
+ export function ProjectForm() {
15
+ const input = useRef<HTMLInputElement>(null);
16
+ return (
17
+ <form
18
+ onSubmit={(event) => {
19
+ event.preventDefault();
20
+ console.log(new FormData(event.currentTarget).get("project"));
21
+ }}
22
+ >
23
+ <Combobox
24
+ ref={input}
25
+ label="Project"
26
+ name="project"
27
+ options={projects}
28
+ defaultValue="atlas"
29
+ required
30
+ hint="Choose an available project."
31
+ />
32
+ <Button type="submit">Inspect selection</Button>
33
+ <Button type="reset" variant="secondary">
34
+ Reset
35
+ </Button>
36
+ </form>
37
+ );
38
+ }
39
+ ```
40
+
41
+ This is local inspection, not persistence. The application's server must validate and authorize the submitted ID. Do not log sensitive form data in a production application.
42
+
43
+ ## Types and defaults
44
+
45
+ [ComboboxProps and ComboboxOption](PUBLIC-API.md) are emitted declarations; inherited native part props remain typed. [Released implementation](https://github.com/abuzareal/contour-ui/blob/v0.6.0/src/components/ui/Combobox.tsx). The ref targets the **visible HTMLInputElement**, including focus, selection and measurement; it does not target the hidden form input or popup.
46
+
47
+ | Props | Contract / default |
48
+ | ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
49
+ | label, options | Required visible label and readonly `{value: string; label: string; disabled?: boolean}` choices. Unique nonempty IDs; labels may repeat or translate. |
50
+ | value / defaultValue / onValueChange | Controlled or uncontrolled selection, null by default. Callback receives string or null. Keep the mode stable for the lifetime of the component. |
51
+ | inputValue / defaultInputValue / onInputValueChange | Independent controlled or uncontrolled query. Default derives from the selected label. Callback also reflects selection/close synchronization; a controlled parent must accept it. |
52
+ | filteredOptions | Omitted: foundation filters the known options by label. Provided: authoritative application results, drawn from options; no second local filtering. |
53
+ | open / defaultOpen / onOpenChange | Controlled or uncontrolled popup; false by default. Disabled/readOnly/hidden ancestor suppresses visible opening. |
54
+ | id, name, form | Generated accessible ID unless supplied. Optional native name and external form owner. No name means no submitted entry. |
55
+ | required, disabled, readOnly, hideLabel, loading | false by default. Hidden labels remain accessible; disabled omits submission. Readonly preserves the value without opening/editing. |
56
+ | hint, error | Optional field help or validation failure. Error takes precedence over hint. Native aria-describedby IDs are merged. |
57
+ | searchError, onRetry | Separate service failure text and optional retry callback. Loading/failure hide selectable results. Retrying is caller-owned; it cannot create a successful result. |
58
+ | loadingText / emptyText / retryLabel / triggerLabel / missingValueText | “Loading choices…”, “No matching choices.”, “Retry search”, “Show choices”, “Selected option is unavailable. Choose another.” Supply translated strings. |
59
+ | inputProps / popupProps / rootProps | Native attributes, events, styles, classes and refs for the existing input, popup and wrapper. Input value/name/form/id/type/state stay owned by the component. Input native events can observe/cancel foundation behavior; use onValueChange for selection. ARIA label references preserve the visible field label. |
60
+ | side / align / sideOffset / collisionPadding | bottom / start / 8 / 8. Positioner flips/shifts within available space. |
61
+ | portal / container | true / automatic owner body or enclosing native dialog. A custom container must share the owner document and be inside its dialog. false keeps the popup inline; ancestor clipping remains the application's responsibility. |
62
+
63
+ ## Anatomy, states and forms
64
+
65
+ The wrapper contains Field's label/help/error, input group, native text input, trigger button and a separate form-value input. The portaled fixed positioner contains a popup, status, optional retry and listbox/options. Input focus stays on the combobox during list navigation; an active-descendant ID identifies the highlighted option. Option content is text, not nested interactive actions. No placeholder is a substitute for the label; use inputProps.placeholder only as supplemental guidance.
66
+
67
+ Uncontrolled form reset restores defaultValue. Controlled selection remains owned by the parent; update it in the form's onReset when a different value is required. The same applies to controlled query/open state. Preventing native reset preserves state. External form association uses form, and a disabled fieldset must retain native successful-control behavior. Required selection is validated by the selection value, not arbitrary query text. Always validate on the server as well.
68
+
69
+ Reordering/translated labels preserve ID identity and update the selected label when the query is not being edited. If the selected ID disappears from **options**, Contour retains the parent's identity, displays the missing-value error, exposes an invalid visible input and serializes an empty value. It does not call onValueChange merely to rewrite parent data. Resolve the record, clear it, or ask the user to choose another; never silently substitute the first choice. A selected option that later becomes disabled remains a historical selection; disabled options cannot be newly selected. The application decides whether its server permits that historical value.
70
+
71
+ For remote search, retain the selected record in options even when it is absent from filteredOptions. The [actual async recipe source](examples/recipes/AsyncProjectPicker.tsx) uses one selected snapshot plus current results, an AbortSignal and stale-result guards. See [recipes](RECIPES.md) for service validation and stable loader identity.
72
+
73
+ Loading and service errors have separate live status text and visible popup messages. Empty results do not imply an empty overall database. The application owns permissions, cache, debounce, request limits, result counts and retry. Keep selected records separate from search results; showing yesterday's results under today's query is misleading.
74
+
75
+ ## Keyboard, layout and limits
76
+
77
+ Typing searches; ArrowDown/ArrowUp moves among enabled choices, Enter selects the highlighted choice, Escape closes only the current popup, and Tab leaves without selecting an arbitrary highlighted item. Composition Enter must not select prematurely. A second Escape can dismiss an enclosing Contour layer; Escape on a closed picker preserves the selected ID. Contour suppresses the foundation's closed-Escape clear action without stopping the native document event. Outside press dismisses the popup; choosing an option keeps the enclosing Dialog/Popover open. Dialog focus containment and return remain owned by that Dialog.
78
+
79
+ Scoped CSS uses `contour-combobox`, `contour-combobox-control`, `contour-combobox-trigger`, `contour-combobox-positioner`, `contour-combobox-popup`, `contour-combobox-option`, `contour-combobox-status` and `contour-combobox-retry`. Options expose data-highlighted/data-selected/data-disabled; the input exposes expanded/invalid/busy state. [Semantic tokens](STYLING.md) control surfaces, text, borders and focus. forms.css is the selective family; scoped.css is the aggregate. Legacy styles.css retains unprefixed compatibility classes. Consumer class names are preserved, never automatically prefixed.
80
+
81
+ Inherited theme and direction travel into portals; collision bounds and long labels support narrow layouts. Options wrap rather than truncate. Forced-colors focus/selection and system reduced motion are respected. Provide localized labels and all status strings; custom matching/remote locale collation belongs to the application. Large collections need a bounded server result set; there is no virtualized/multiple/grouped API in this release.
82
+
83
+ Automated Chromium keyboard, touch emulation, synthetic composition, RTL, forced colors, native-dialog and packed React tests are evidence for those fixtures only. Manual screen-reader, physical keyboard/IME and mobile keyboard checks remain separate; no universal accessibility certification is claimed.
84
+
85
+ **Open-state accessibility review:** the foundation temporarily hides background content from the accessibility tree while the list is active. Static axe checks report `aria-hidden-focus` on that background. Browser regressions verify that Tab/Shift+Tab close the list and restore visibility before focus remains on an outside control. This is retained as a finding requiring manual VoiceOver/NVDA review, not waived as a universal false positive or reported as a zero-finding scan. The [ACT rule's assumptions](https://www.w3.org/WAI/standards-guidelines/act/rules/6cfa84/) explicitly distinguish interactions that change aria-hidden. Do not claim WCAG conformance from these fixtures.
86
+
87
+ [Report Combobox 0.6.0](https://github.com/abuzareal/contour-ui/issues/new?template=bug_report.yml&title=Combobox%200.6.0%3A%20&component=Combobox&version=0.6.0), using the [public minimal reproduction](examples/reproduction). Source links for this version resolve when its release tag is published.
package/CONTRACTS.md CHANGED
@@ -1,16 +1,16 @@
1
1
  # Architecture, scope and compatibility
2
2
 
3
- Contract version **0.4.0**, reviewed 13 October 2026. Contour is an early 0.x React library with typed controls, paper/ink surfaces, a lime accent, light/dark themes, restrained motion and separate optional depth/scene APIs. The [public API and maturity register](PUBLIC-API.md) lists every exported value, type and stylesheet, its maintainer, status, use and limits.
3
+ Contract version **0.6.0**, reviewed 14 October 2026. Contour is an early 0.x React library with typed controls, paper/ink surfaces, a lime accent, light/dark themes, restrained motion and separate optional depth/scene APIs. The [public API and maturity register](PUBLIC-API.md) lists every exported value, type and stylesheet, its maintainer, status, use and limits.
4
4
 
5
5
  ## Intended adoption scope
6
6
 
7
- Use Contour in React web applications that want its supplied appearance and can manage global CSS and one document-level theme. Starting workflows are editable text/native choices, application-owned search/results, scalar locally sorted tables, modal settings and feedback. Existing editorial motion/identity exports remain available within their limits.
7
+ Use Contour in React web applications that want its supplied appearance and can use either isolated scoped CSS/themes or the documented legacy global integration. Starting workflows are editable text/native choices, application-owned search/results, scalar locally sorted tables, modal settings and feedback. Existing editorial motion/identity exports remain available within their limits.
8
8
 
9
- The first acceptance workflow is settings/search/table composition: edit a named TextField/SelectField/Checkbox, submit or reset the native form, own pending/error/retry in the app, filter supplied scalar records, change the root theme and open a settings Dialog. The catalogue already has [application patterns](https://contour.abuzr.in/docs/patterns) with local simulated adapters, not a backend. Switch and Slider settings currently use application state; RadioGroup uses a generated field name. Do not claim a stable caller-named FormData contract for those three controls.
9
+ The first acceptance workflow is settings/search/table composition: edit a named TextField/SelectField/Checkbox, submit or reset the native form, own pending/error/retry in the app, filter supplied scalar records, change the root theme and open a settings Dialog. The catalogue already has [application patterns](https://contour.abuzr.in/docs/patterns) with local simulated adapters, not a backend. Switch, Slider and RadioGroup have named/external native form and reset contracts since 0.4; their controlled state remains application-owned.
10
10
 
11
11
  Fetching, response validation, permissions, domain validation, persistence, URL state, server operations and truthful async outcomes belong to the application. A toast or demonstration does not mean data was saved. Apps supply labels, useful content, keyboard paths and accessible compositions.
12
12
 
13
- Current scope excludes a general Combobox, multi-select, date/range picker, remote grid, tree, command palette, upload backend, chart/editor engine and non-React adapters. DataTable supports scalar string/number rows and local sorting. Drawer is Dialog placement; banner is Alert layout. New capabilities require a bounded contract, tests, documentation and demonstrated need.
13
+ Current scope includes one allowed-value single-select Combobox and a native Table presentation frame. It excludes multi-select, date/range picker, remote grid, tree, command palette, upload backend, chart/editor engine and non-React adapters. DataTable supports scalar string/number rows and local sorting. Drawer is Dialog placement; banner is Alert layout. New capabilities require a bounded contract, tests, documentation and demonstrated need.
14
14
 
15
15
  No promise of enterprise completeness, universal embedding, all locales/RTL, every browser/framework or accessibility certification is made. Early 0.x status and experimental limits are adoption information.
16
16
 
@@ -30,14 +30,14 @@ Catalogue UI/hook/scene compatibility modules intentionally forward public packa
30
30
 
31
31
  React is the UI runtime. Strict TypeScript/TSX source compiles through `tsc` into ES2022 ESM modules and declarations. The build copies CSS and selected licensed Latin font assets. It does not bundle React or compile the library into a Vite app. Vite serves development/consumer fixtures; Design-System is the Vite application. Consumers can write JavaScript or TypeScript.
32
32
 
33
- | Category | Current packages | Consumer consequence |
34
- | ------------------------ | ------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
35
- | Runtime dependencies | lucide-react; @fontsource-variable/dm-sans; @fontsource-variable/space-grotesk; @fontsource/dm-mono | Install with Contour. Selected imports determine browser output. Font CSS is optional; font installation currently is not. |
36
- | Required peers | react/react-dom: React 18 (`^18.3.1`) or React 19 (`^19.0.0`) | App supplies a matching pair; allowed range is broader than individually tested versions. |
37
- | Optional peers | three: `^0.186.1`; lenis: `^1.3.26` | Core works without either. Install explicitly for the matching optional entry. |
38
- | Development dependencies | TypeScript, Vite, Oxlint, Testing Library, jsdom, Playwright, React/types and optional peer dev copies | Source authoring/tests/build; not downstream Contour production dependencies. |
33
+ | Category | Current packages | Consumer consequence |
34
+ | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
35
+ | Runtime dependencies | @base-ui/react 1.9.0 (Combobox); @floating-ui/dom 1.8.0 (positioning); lucide-react; @fontsource-variable/dm-sans; @fontsource-variable/space-grotesk; @fontsource/dm-mono | Install with Contour. Selected imports determine browser output. Font CSS is optional; font installation currently is not. |
36
+ | Required peers | react/react-dom: React 18 (`^18.3.1`) or React 19 (`^19.0.0`) | App supplies a matching pair; allowed range is broader than individually tested versions. |
37
+ | Optional peers | three: `^0.186.1`; lenis: `^1.3.26` | Core works without either. Install explicitly for the matching optional entry. |
38
+ | Development dependencies | TypeScript, Vite, Oxlint, Testing Library, jsdom, Playwright, React/types, table-recipe development adapters and optional peer dev copies | Source authoring/tests/build; not downstream Contour production dependencies. |
39
39
 
40
- Use explicit public paths; deep imports into dist internals are unsupported. CSS is marked as side-effectful to retain imported styles. The archive contains built modules/declarations/assets and release documents, plus copyable form recipe sources, with no consumer install scripts or private research. React Hook Form is an example/application dependency, not a runtime dependency.
40
+ Use explicit public paths; deep imports into dist internals are unsupported. CSS is marked as side-effectful to retain imported styles. The archive contains built modules/declarations/assets and release documents, plus public form/adoption/optional recipe sources, standalone starters and a minimal reproduction, with no consumer install scripts or private research. React Hook Form and TanStack Table/Store are example/application dependencies, not Contour runtime dependencies. See FOUNDATION.md for the bounded foundation decision and licensing.
41
41
 
42
42
  Archive bytes, installed dependency bytes, browser JS/CSS, emitted fonts and actual requested/cached network assets are different measurements. An installed icon collection is not proof all icons download. JavaScript tree shaking does not remove unused aggregate CSS rules automatically. Size claims require version, fixture, tool versions, compression and exclusions; independent component gzip samples cannot be added as an application total.
43
43
 
@@ -60,9 +60,9 @@ Interactive modules retain emitted client directives. Server-importable pure hel
60
60
 
61
61
  ## Styles, theme and motion
62
62
 
63
- `styles.css` supplies tokens/components/utilities; `tokens.css` supplies tokens alone. Reset and either font stylesheet are optional. Import deliberate app overrides afterward. Current classes such as button/card/menu/mono and variables --bg/--fg are global. Reset contains inherited page-width/site-shell/section offsets; tokens retain portfolio/header values. These contracts are preserved; later isolation requires a versioned compatibility path.
63
+ `styles.css` supplies tokens/components/utilities; `tokens.css` supplies tokens alone. Reset and either font stylesheet are optional. Import deliberate app overrides afterward. The legacy aggregate retains global classes such as button/card/menu/mono and --bg/--fg for compatibility. The scoped aggregate and selective families use contour-prefixed classes and semantic tokens; minimal-reset.css is opt-in. Use STYLING.md and SIZE.md to choose imports instead of applying a legacy page reset to an embedded control.
64
64
 
65
- Theme follows root data-theme and legacy aa-theme origin storage. Independent subtree themes are not a general contract; choose one owner in a multi-library page. Optional branded fonts are DM Sans, Space Grotesk and DM Mono. Existing font tokens also permit system fonts. Select Latin or broader coverage intentionally and preserve notices.
65
+ Theme follows root data-theme and legacy aa-theme origin storage. ThemeScope provides a controlled independent subtree with scoped CSS and inherited portal values; its caller owns initial state/persistence. Root useTheme remains document-owned. Optional branded fonts are DM Sans, Space Grotesk and DM Mono. Existing font tokens also permit system fonts. Select Latin or broader coverage intentionally and preserve notices.
66
66
 
67
67
  Respect system reduced motion and app pause. CSS depth is core; WebGL is separate. Lazy scenes need sized hosts, useful static content outside SceneBoundary, capped rendering cost, no unnecessary hidden/offscreen work and symmetric cleanup. Register owned custom cleanup immediately through onDispose, including failed initialization; avoid double disposal. Mount-captured setup changes need explicit remount.
68
68
 
@@ -72,13 +72,13 @@ Preserve native input/select/details/dialog behavior where appropriate. Future r
72
72
 
73
73
  - Actions, ExternalLink, native fields/choices, Dialog and the documented Tabs/Popover parts forward their native targets on React 18/19. Multi-control/presentation wrappers do not imply a singular input ref; see [the composition contract](COMPOSITION.md).
74
74
  - Checkbox accepts caller IDs while retaining a generated fallback and internal indeterminate behavior alongside the forwarded input ref; caller ARIA validity and descriptions remain composed. Switch/Slider/RadioGroup support named/external form submission and controlled/default/reset behavior; see [the form guide](FORMS.md). Switch refs remain buttons and its checkbox backing submits only while checked; a range has no required constraint.
75
- - Popover/DropdownMenu use local absolute panels without portal/collision/submenu/custom-trigger contracts. Independent document listeners close both nested Popovers on one Escape: a reproduced defect.
76
- - Tooltip closes on pointer leave and CSS prevents content hover: a source finding/accessibility risk requiring correction and pointer/focus verification, not a formal conformance audit. Hints must be supplemental and noninteractive.
77
- - Dialog/Tabs/Popover expose typed root and owned-part props/refs; state and ARIA wiring stay protected and caller cancellation composes with internal behavior. Arbitrary trigger replacement, portals/collision and general polymorphism remain outside this API.
75
+ - Popover/DropdownMenu/Tooltip use collision-aware owner-body/native-dialog portals by default, preserve logical Contour nesting and dismiss one topmost layer. Use portal=false deliberately for inline content. General submenus/third-party modal interoperability are not promised; see OVERLAYS.md.
76
+ - Tooltip remains hoverable while pointer/focus is in its trigger/content and supports Escape suppression. Hints remain supplemental/noninteractive; a hover check does not constitute formal accessibility certification.
77
+ - Dialog/Tabs/Popover expose typed root and owned-part props/refs; state and ARIA wiring stay protected and caller cancellation composes with internal behavior. The public renderTrigger contract permits one ref-forwarding button where documented; arbitrary root replacement and general polymorphism remain outside the API.
78
78
  - Disclosure is native details with defaultOpen and required className, not controlled open/onOpenChange. Controlled open state belongs to Popover/DropdownMenu.
79
79
  - BrandMark/editorial section/heading helpers and selected navigation/print hooks retain inherited page assumptions. They remain supported legacy APIs, not universal product primitives.
80
80
 
81
- Recording these limits does not complete their later API/form/overlay/style fixes.
81
+ Combobox forwards its visible input and Table its native table; their form/state/part boundaries are documented in COMBOBOX.md and RECIPES.md. Documentation promotion and manual assistive-technology verification remain separate work.
82
82
 
83
83
  ## Change, deprecation and migration policy
84
84
 
@@ -91,3 +91,9 @@ Formal deprecation identifies replacement/affected versions, marks the API/docs
91
91
  Releases choose an unused version, align manifest/lock/tag/changelog, validate a candidate archive and state material limits. Consumers keep exact pins until the released version is installed, their lockfile updated and relevant checks pass. Package publication does not deploy an application.
92
92
 
93
93
  1.0 requires settled supported contracts, appropriate docs/evidence, successful outside-use workflows and no known critical/high-impact defect in the promoted scope. Complete stabilization, then rerun final release gates. Deferred component backlog completion is not a prerequisite.
94
+
95
+ ## 0.5 overlay/style/size contract
96
+
97
+ Default floating portals render in the owner body or active native modal, carrying scoped values/direction. Topmost dismissal, focus customization, closed-ancestor suspension and hover persistence follow OVERLAYS.md. Default portal DOM placement is an explicit 0.x migration; local placement remains available. Stable ref targets and the public void useDismiss return remain unchanged.
98
+
99
+ Legacy aggregate/reset/tokens/classes remain compatibility mode. Isolated CSS, prefixed schema, controlled ThemeScope, optional minimal reset and measured family entries follow STYLING.md. Product/layout tokens remain legacy/app-owned. Scope persistence is caller-owned; document hooks retain aa-theme behavior. Compact density and a font-install packaging migration were evaluated and deferred with reasons in STYLING.md/SIZE.md. No zero-dependency, universal RTL/AT or production performance claim is added.
package/DOCS.md ADDED
@@ -0,0 +1,80 @@
1
+ # Public documentation contract and coverage
2
+
3
+ For Contour 0.6.0, the public package/repository is an independent source of installation, API, recipe and reproduction material. The separate catalogue can describe an older installed release; its visual demos are not proof that this source version is installed there. Catalogue editing and per-component website promotion are separate work.
4
+
5
+ ## Contract for a promoted guide
6
+
7
+ Every promoted stable component page must contain: purpose and when not to use; public imports and executable TSX; emitted prop types, defaults and inherited native props; anatomy/parts; controlled/default state; native ref target; name/form/required/disabled/reset behavior; keyboard/focus/dismissal; CSS classes/tokens/state selectors; loading/empty/error/long content; RTL/localization; exact version/source; limitations and a version/component issue link with a public reproduction. Mark an item inapplicable with a reason rather than omitting it. A recipe is not a component export. An automated accessibility check is not a manual assistive-technology report.
8
+
9
+ [Combobox](COMBOBOX.md) is the new full component guide. [Table and application recipes](RECIPES.md) share a guide because Table is a native presentation frame and each recipe owns a different application workflow. [GETTING-STARTED.md](GETTING-STARTED.md) covers standalone/embedded setup, providers by need, theme/bootstrap/CSP, navigation and optional features. [PUBLIC-API.md](PUBLIC-API.md) inventories every runtime/type export with versioned source and status; types without runtime values are not missing components.
10
+
11
+ ## Source and drift checks
12
+
13
+ Displayed recipe links point to the actual shipped TSX modules, not a second copy. Packed tests copy those files into fresh React 18/19 consumers, compile with strict declarations and exercise behavior. Complete TSX fences in Combobox/onboarding references also compile. Starters are copied from the archive, installed independently and production-tested; final registry mode runs after publication. Source tags/issue versions match package.json; candidate tag links become available at publication. New release changes must update public guide labels, README, declarations and changelog together.
14
+
15
+ The coverage register below maps public UI components to an explicit shared reference or dedicated guide. “Shared; promotion pending” means discovery/type coverage exists but a full component page has not been completed. It is a **documentation gap**, not an absent export or claim of stable-page completeness. Hook/helper/type coverage is retained in the complete PUBLIC-API register; optional entry setup/lifecycle is shared with GETTING-STARTED. The package's check:docs script compares the component list with source exports, requires guide targets and compiles advertised copyable source through the release gates.
16
+
17
+ <!-- component-coverage:start -->
18
+
19
+ | Component | Public reference | Coverage |
20
+ | ---------------- | ---------------------------------------- | ------------------------------ |
21
+ | Alert | [PUBLIC-API.md](PUBLIC-API.md) | Shared; promotion pending |
22
+ | Avatar | [PUBLIC-API.md](PUBLIC-API.md) | Shared; promotion pending |
23
+ | Badge | [PUBLIC-API.md](PUBLIC-API.md) | Shared; promotion pending |
24
+ | BrandMark | [PUBLIC-API.md](PUBLIC-API.md) | Shared; promotion pending |
25
+ | Breadcrumbs | [PUBLIC-API.md](PUBLIC-API.md) | Shared; promotion pending |
26
+ | Button | [COMPOSITION.md](COMPOSITION.md) | Shared; promotion pending |
27
+ | Card | [PUBLIC-API.md](PUBLIC-API.md) | Shared; promotion pending |
28
+ | Checkbox | [FORMS.md](FORMS.md) | Shared; promotion pending |
29
+ | Combobox | [COMBOBOX.md](COMBOBOX.md) | New component contract covered |
30
+ | CountUp | [PUBLIC-API.md](PUBLIC-API.md) | Shared; promotion pending |
31
+ | DataTable | [RECIPES.md](RECIPES.md) | Shared; promotion pending |
32
+ | DepthButton | [COMPOSITION.md](COMPOSITION.md) | Shared; promotion pending |
33
+ | Dialog | [OVERLAYS.md](OVERLAYS.md) | Shared; promotion pending |
34
+ | Disclosure | [PUBLIC-API.md](PUBLIC-API.md) | Shared; promotion pending |
35
+ | Divider | [PUBLIC-API.md](PUBLIC-API.md) | Shared; promotion pending |
36
+ | DropdownMenu | [OVERLAYS.md](OVERLAYS.md) | Shared; promotion pending |
37
+ | EmptyState | [PUBLIC-API.md](PUBLIC-API.md) | Shared; promotion pending |
38
+ | ExternalLink | [COMPOSITION.md](COMPOSITION.md) | Shared; promotion pending |
39
+ | Field | [FORMS.md](FORMS.md) | Shared; promotion pending |
40
+ | FlipCard | [PUBLIC-API.md](PUBLIC-API.md) | Shared; promotion pending |
41
+ | GitHubIcon | [PUBLIC-API.md](PUBLIC-API.md) | Shared; promotion pending |
42
+ | IconButton | [COMPOSITION.md](COMPOSITION.md) | Shared; promotion pending |
43
+ | Kbd | [PUBLIC-API.md](PUBLIC-API.md) | Shared; promotion pending |
44
+ | LinkedInIcon | [PUBLIC-API.md](PUBLIC-API.md) | Shared; promotion pending |
45
+ | Magnetic | [PUBLIC-API.md](PUBLIC-API.md) | Shared; promotion pending |
46
+ | Marquee | [PUBLIC-API.md](PUBLIC-API.md) | Shared; promotion pending |
47
+ | Pagination | [PUBLIC-API.md](PUBLIC-API.md) | Shared; promotion pending |
48
+ | Popover | [OVERLAYS.md](OVERLAYS.md) | Shared; promotion pending |
49
+ | ProgressBar | [PUBLIC-API.md](PUBLIC-API.md) | Shared; promotion pending |
50
+ | RadioGroup | [FORMS.md](FORMS.md) | Shared; promotion pending |
51
+ | Reveal | [PUBLIC-API.md](PUBLIC-API.md) | Shared; promotion pending |
52
+ | RevealHeading | [PUBLIC-API.md](PUBLIC-API.md) | Shared; promotion pending |
53
+ | SceneBoundary | [PUBLIC-API.md](PUBLIC-API.md) | Shared; promotion pending |
54
+ | ScrollProgress | [PUBLIC-API.md](PUBLIC-API.md) | Shared; promotion pending |
55
+ | SectionLabel | [PUBLIC-API.md](PUBLIC-API.md) | Shared; promotion pending |
56
+ | SegmentedControl | [PUBLIC-API.md](PUBLIC-API.md) | Shared; promotion pending |
57
+ | SelectField | [FORMS.md](FORMS.md) | Shared; promotion pending |
58
+ | Skeleton | [PUBLIC-API.md](PUBLIC-API.md) | Shared; promotion pending |
59
+ | Slider | [FORMS.md](FORMS.md) | Shared; promotion pending |
60
+ | Spinner | [PUBLIC-API.md](PUBLIC-API.md) | Shared; promotion pending |
61
+ | Stat | [PUBLIC-API.md](PUBLIC-API.md) | Shared; promotion pending |
62
+ | Stepper | [PUBLIC-API.md](PUBLIC-API.md) | Shared; promotion pending |
63
+ | Switch | [FORMS.md](FORMS.md) | Shared; promotion pending |
64
+ | Table | [RECIPES.md](RECIPES.md) | New component contract covered |
65
+ | Tabs | [COMPOSITION.md](COMPOSITION.md) | Shared; promotion pending |
66
+ | Tag | [PUBLIC-API.md](PUBLIC-API.md) | Shared; promotion pending |
67
+ | TextArea | [FORMS.md](FORMS.md) | Shared; promotion pending |
68
+ | TextField | [FORMS.md](FORMS.md) | Shared; promotion pending |
69
+ | ThemeScope | [GETTING-STARTED.md](GETTING-STARTED.md) | Shared; promotion pending |
70
+ | ThemeToggle | [GETTING-STARTED.md](GETTING-STARTED.md) | Shared; promotion pending |
71
+ | TiltCard | [PUBLIC-API.md](PUBLIC-API.md) | Shared; promotion pending |
72
+ | Timeline | [PUBLIC-API.md](PUBLIC-API.md) | Shared; promotion pending |
73
+ | ToastProvider | [PUBLIC-API.md](PUBLIC-API.md) | Shared; promotion pending |
74
+ | Tooltip | [OVERLAYS.md](OVERLAYS.md) | Shared; promotion pending |
75
+
76
+ <!-- component-coverage:end -->
77
+
78
+ ## Remaining promotion work
79
+
80
+ The original website per-component pages, search/pattern discovery, legacy URL preservation, generated prop metadata and versioned website archives remain separate work. Existing shared guides must be audited against this contract before each component is promoted. Manual screen-reader/physical device evidence is tracked separately from automated fixture results. This register deliberately leaves those gaps visible.
package/FOUNDATION.md ADDED
@@ -0,0 +1,35 @@
1
+ # Interaction foundation
2
+
3
+ Decision for the 0.6.0 candidate, evaluated 14 October 2026: use **Base UI 1.9.0** for the new single-select Combobox. Existing native controls, Contour overlays, public APIs and CSS remain owned by Contour. This is a bounded decision, not a migration of every component or a claim that one foundation is universally better.
4
+
5
+ ## Comparison
6
+
7
+ The same published Contour 0.5.0 host, data, CSS, nested Popover and application-owned asynchronous search were exercised with React 18.3.1 and 19.3.0 in Chromium 153. The candidates were installed from npm archives with strict peers. Each fixture was server-rendered and hydrated; refs, form reset, native modal placement, nested dismissal, stale requests, disabled options, synthetic IME events, RTL and forced colors were checked. Query cancellation belongs to the common application fixture, not to any foundation.
8
+
9
+ | Candidate | Fit and cost in this fixture | Integration work |
10
+ | ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
11
+ | Base UI 1.9.0, MIT | Allowed-value Combobox, value/label collection mapping, forms and unstyled parts. Adds 6 packages / 11,574,796 installed bytes over the common baseline. | Preserve Contour layer ordering and inherited theme, use an owner-document/native-dialog portal, and define missing-value policy. Core nested Escape and native modal cases passed on both React majors. |
12
+ | Radix Popover 1.3.1 + cmdk 1.1.1, MIT | Radix supplies overlays, not a Combobox. Adds 33 packages / 1,381,618 installed bytes. | A separate command-menu selection adapter supplies value mapping, hidden form input, validation and query semantics. React 18 nested dismissal was inconsistent in this prototype; this is bounded integration evidence, not a general Radix defect claim. |
13
+ | React Aria Components 1.22.1, Apache-2.0 | Collection, localization and form facilities. Adds 13 packages / 30,671,216 installed bytes. | Controlled query/selection synchronization and portal configuration are explicit. Escape also dismissed the Contour parent in this fixture, requiring a dismissal bridge. Portal-container API migration adds adapter maintenance. |
14
+
15
+ Installed bytes include declarations and upstream components that are not necessarily downloaded by the browser. The comparison is not a zero-dependency or tree-shaking claim. The library's production size gate separately measures Button, forms, table and Combobox journeys from its packed artifact. Base UI's date-fns and timezone peers are optional and are not needed for Combobox. No second foundation or table engine becomes a core dependency.
16
+
17
+ ## Ownership and limits
18
+
19
+ Contour exports a single-select string-ID API, typed native input/popup parts and its own semantic styling. Base UI owns collection navigation, active-descendant behavior and selection mechanics. Contour bridges that popup into its dismissal stack and modal/top-layer boundary. Its adapter also prevents the foundation's closed-Escape clear action so an enclosing Contour layer can dismiss without silently clearing the selected ID. Existing Dialog, SelectField and Popover contracts are not replaced with upstream types.
20
+
21
+ Stable IDs must be unique and nonempty. Labels may change or repeat. Filtering must not redefine identity. For remote search, the application retains the selected option separately from current results, cancels or ignores obsolete requests and validates its server data. An unresolved selected ID is visibly invalid and contributes an empty form value until resolved or changed; it is never invented as a valid choice. Disabled choices cannot be newly selected; disabled controls are omitted from form submission.
22
+
23
+ Automated checks do not establish VoiceOver/NVDA certification, physical IME composition, mobile keyboard behavior or all browser/assistive-technology combinations. Those remain manual release evidence gaps. A descendant popup must be tested with any third-party modal library before claiming interoperability.
24
+
25
+ ## Upstream maintenance and licensing
26
+
27
+ The primary implementations are TypeScript/React with JavaScript distributions and declarations. Base UI has versioned release/migration notes and active 1.9 releases; pinning the adapter dependency makes upgrades reviewable. React Aria and Radix likewise have their own versioned releases. cmdk is a separate project with its own cadence; an older release alone does not prove it is unmaintained. Supported React peer ranges do not prove every framework or browser configuration.
28
+
29
+ Retain upstream copyright/license notices when redistributing their code. These packages retain their own licenses; Contour's authored code remains MIT. npm dependency packages include their upstream notices. We do not copy a foundation's source into Contour or claim its accessibility work as independent certification.
30
+
31
+ Primary references: [Base UI Combobox](https://base-ui.com/react/components/combobox), [releases](https://base-ui.com/react/overview/releases), [MIT license](https://github.com/mui/base-ui/blob/master/LICENSE); [Radix Popover](https://www.radix-ui.com/primitives/docs/components/popover), [MIT license](https://github.com/radix-ui/primitives/blob/main/LICENSE), [cmdk](https://github.com/dip/cmdk); [React Aria ComboBox](https://react-aria.adobe.com/ComboBox), [Apache-2.0 license](https://github.com/adobe/react-spectrum/blob/main/LICENSE).
32
+
33
+ Local open-state axe inspection reports aria-hidden-focus on temporarily hidden background controls. Tab/Shift+Tab/outside-focus behavior must be considered alongside that static result; the raw finding remains recorded and manual AT review is pending. See COMBOBOX.md. The adapter uses the public virtual-anchor and collision-Rect extension points to keep fixed-position coordinates correct under inherited CSS zoom; it does not copy or patch upstream internals.
34
+
35
+ Contour also owns synchronous input activation to avoid an obsolete upstream touch-open timer reopening a selected/closed picker. Public event cancellation/propagation hooks preserve native document observers and caller Escape vetoes. These adapters are regression-tested without exposing upstream event types in Contour props.
@@ -0,0 +1,63 @@
1
+ # Adopt Contour — 0.6.0
2
+
3
+ Use [the standalone Vite starter](examples/starters/vite), [Next App Router starter](examples/starters/next), or add one component to an existing app. Each folder is public and independent; no private catalogue source, repository aliases, provider stack or application layout is required. Direct dependencies are exact; generate and commit your application's lockfile, then use npm ci. Candidate source requires an archive until its matching version is published.
4
+
5
+ ## Existing application
6
+
7
+ ```sh
8
+ npm install --save-exact @abuzareal/contour-ui@0.6.0
9
+ ```
10
+
11
+ Install matching react/react-dom versions in the supported ranges (^18.3.1 or ^19.0.0). React 18.3.1, 19.0.0 and 19.3.0 have independent packed consumer checks; a peer range is not a claim of every framework configuration. Native form/overlay APIs are client interactions. Use the library's emitted declarations instead of a copied API from an old catalogue.
12
+
13
+ ```tsx
14
+ import { Button, ThemeScope } from "@abuzareal/contour-ui";
15
+ import "@abuzareal/contour-ui/scoped.css";
16
+ export function EmbeddedAction() {
17
+ return (
18
+ <ThemeScope theme="light">
19
+ <Button onClick={() => console.log("Local action")}>Continue</Button>
20
+ </ThemeScope>
21
+ );
22
+ }
23
+ ```
24
+
25
+ This fixed light scope is intentional: it does not own the host document theme/storage. To let ThemeToggle change it, supply controlled theme and onThemeChange to ThemeScope. A fixed scope without a callback disables its toggle. No document bootstrap is needed for a deterministic embedded scope. Keep your router, reset and fonts. scoped.css supplies tokens and all prefixed component styles; selective entry imports are documented in [SIZE.md](SIZE.md). The opt-in minimal-reset.css and legacy reset.css have different scopes; do not import a global reset accidentally. System fonts work without fonts.css/fonts-latin.css. Native button/anchor form roles remain intact.
26
+
27
+ ## Providers, forms and navigation
28
+
29
+ Button, fields, Combobox, Table, Dialog, Popover and ordinary controls work without a provider. Dialog/Popover open state belongs to the caller. **ToastProvider** is required only around useToast consumers; mount one owner for the part of the app that needs its queue. ThemeScope is an optional embedded-theme boundary, not a universal installation requirement. No Base UI provider or table engine is required to consume Combobox. Direction/theme is inherited into its portal.
30
+
31
+ The [native/RHF form recipes](FORMS.md) use the actual files in examples/forms. Native name/form/required/disabled/reset contracts remain native; RadioGroup/Slider/Switch differences are documented there. React Hook Form is an application dependency only for the RHF recipe; it is absent from core consumers. Application adapters own cross-field validation, parsed data, persistence, pending/error/retry and server validation. The [new recipes](RECIPES.md) add async selection, safe confirmation and remote tables without turning them into hidden services.
32
+
33
+ Use Button for actions and native anchors or the router's Link for navigation. [COMPOSITION.md](COMPOSITION.md#native-and-router-links) has executable anchor/Next examples using buttonClassName("secondary"). Keep href/to, modified clicks, context menus and ref forwarding; never nest an anchor in a button. The Vite starter uses a native hash link; the Next starter uses Next Link with ordinary client navigation. Other router adapters need their own tested ref/anchor contract; do not infer universal support.
34
+
35
+ ## Document theme and CSP
36
+
37
+ For a document-owned theme, import themeBootstrapScript/themeColors from the **pure server-safe** `@abuzareal/contour-ui/theme-bootstrap` subpath. Inject that exact script before the first paint. It reads aa-theme only for light/dark, falls back to the system preference, sets html[data-theme] and updates an existing theme-color meta. themeColors is light #f2f3ee / dark #0f100d. Blocked storage falls back without throwing; useTheme's root behavior owns document attributes/storage and follows system changes when there is no stored choice. It is not an arbitrary persisted settings provider. ThemeToggle's accessible name is constant (“Dark theme” by default); aria-pressed expresses state and title describes the next action.
38
+
39
+ Hash **the exact script bytes** with SHA-256 and authorize that hash in script-src, or use a cryptographically random per-request nonce on the bootstrap and framework scripts. Do not copy a stale hash across versions or reuse/cache a nonce. The actual [Vite config](examples/starters/vite/vite.config.ts) calculates and inserts the hash; [Next proxy](examples/starters/next/proxy.ts) and [layout](examples/starters/next/app/layout.tsx) carry a new nonce through server rendering. Next's nonce setup is dynamic, not static export/ISR/shared-cache output. Both paths are production browser checks, not merely snippets. Only the html attribute touched before hydration uses suppressHydrationWarning in Next.
40
+
41
+ Production scripts do not require unsafe-inline/unsafe-eval in these starters. **Inline styles are allowed** for library geometry and host styles; this is a restricted script policy, not a claim that every CSS directive can be nonce-only. Configure font/image/service origins deliberately. Root bootstrap does not initialize independent ThemeScope state; choose a deterministic scope or derive initial state explicitly in your app. Do not hide arbitrary hydration mismatches.
42
+
43
+ ## Optional features
44
+
45
+ Core imports require neither Three.js nor Lenis. Opt in only for a real product need:
46
+
47
+ ```sh
48
+ npm install --save-exact three@0.186.1
49
+ # Only for the optional scrolling recipe:
50
+ npm install --save-exact lenis@1.3.26
51
+ ```
52
+
53
+ Copy [LazyScene.tsx](examples/optional/LazyScene.tsx) and [optional.css](examples/optional/optional.css). It imports /three only after “Load 3D study”, gives the host a real block size, retains useful static artwork outside SceneBoundary/Suspense, and passes both app pause and system reduced-motion state. SceneBoundary hides failed scenes; it has no fallback prop and cannot manufacture a visual replacement. Sculpture/ObjectStudy are decorative scene components; product meaning stays in adjacent DOM content. The optional entry includes the public scene APIs/types; use [PUBLIC-API.md](PUBLIC-API.md) for their exact experimental contracts.
54
+
55
+ useThreeStage captures setup/cameraDistance once per mount. setup receives scene/camera/renderer and optional onDispose registration. Register cleanup immediately after every owned GPU allocation, including resources created before setup can throw. A returned StageScene supplies update, rest, dispose and optional resize. Do not double-dispose the same resource through both onDispose and dispose. The stage caps pixel ratio at 1.75, observes sizing, stops unnecessary hidden/offscreen animation and redraws a still pose when paused. The caller owns stable setup inputs, meaningful sizing, lazy boundaries, static fallback and content. Unavailable WebGL/partial setup must leave useful DOM content. No physical GPU/device coverage is implied by headless fixtures.
56
+
57
+ Copy [OptionalSmoothScroll.tsx](examples/optional/OptionalSmoothScroll.tsx) and [SmoothScrollOwner.tsx](examples/optional/SmoothScrollOwner.tsx) only after installing Lenis. The lazy owner imports lenis/dist/lenis.css and calls /scroll's useSmoothScroll once. Native scroll works until opt-in. Reduced motion disables/unmounts that owner; unmount destroys Lenis. Do not mount multiple RAF/scroll owners, fight native focus/hash restoration or silently enable smooth motion for users who requested reduction. This recipe is for the Vite/client integration; Next Lenis is **unverified**.
58
+
59
+ ## Framework boundaries and reproduction
60
+
61
+ Supported evidence names the exact pair: Next 16.4.0 App Router, React/React DOM 19.3.0, webpack production build, plus its separate optional lazy Three fixture. Turbopack, Pages Router, Next Lenis, static nonce export and arbitrary third-party modal interoperability are not promoted to supported configurations. Vite starter pins 8.3.2 with React 18.3.1. Server-safe helper imports, client directives, declarations and hydration are checked independently; SSR rendering alone does not prove hydration.
62
+
63
+ Copy [the public reproduction folder](examples/reproduction), reduce it to the failing interaction, and include exact package/browser versions and keyboard steps. Its [issue link](https://github.com/abuzareal/contour-ui/issues/new?template=bug_report.yml&title=Integration%200.6.0%3A%20&component=Integration&version=0.6.0) contains version/component, not private data. Report only synthetic records and public reproducible source. [Documentation coverage and limits](DOCS.md).
package/NEXTJS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Next.js App Router integration
2
2
 
3
- This guide describes **Contour 0.4.0**. npm 0.1.2 does not include the client directives, native refs or relative Latin font files described here. Check npm for release availability before installing. The fixture pins Next 16.4.0 with React/React DOM 19.3.0, uses the webpack production build, and consumes an npm archive. It does not establish every Next version, Turbopack, Pages Router, scroll entries or hosting platform. A separate optional mode checks a lazy Three.js scene with SSR disabled and disposal on navigation.
3
+ This guide describes **Contour 0.6.0**. npm 0.1.2 does not include the client directives, native refs or relative Latin font files described here. Check npm for release availability before installing. The fixture pins Next 16.4.0 with React/React DOM 19.3.0, uses the webpack production build, and consumes an npm archive. It does not establish every Next version, Turbopack, Pages Router, scroll entries or hosting platform. A separate optional mode checks a lazy Three.js scene with SSR disabled and disposal on navigation.
4
4
 
5
5
  In `app/layout.tsx`, load CSS once:
6
6
 
@@ -53,3 +53,7 @@ Run `npm run test:next` to rebuild and consume the candidate archive. The fixtur
53
53
  The 0.4.0 fixture also verifies Button native-ref focus and styled Next Link ordinary/canceled/middle-click navigation. Use [the composition guide](COMPOSITION.md#native-and-router-links) for the Link recipe; Button remains an action rather than a polymorphic navigation component.
54
54
 
55
55
  Named form/reset APIs and copyable form recipes are described in [FORMS.md](FORMS.md). They remain client interactions; server-side validation and persistence belong to your application.
56
+
57
+ For embedded first use, substitute scoped.css, omit fonts/bootstrap/reset, and wrap client controls in controlled ThemeScope. The size fixture separately measures Button/settings/table routes with system fonts on the same pinned webpack configuration. Existing document-owned/branded integration above remains compatible; new portal migration and family imports are described in OVERLAYS.md and SIZE.md.
58
+
59
+ The standalone [Next starter](examples/starters/next) adds a tested per-request nonce CSP and dynamic layout using Next 16.4.0/React 19.3.0/webpack and TypeScript 7.0.2. Its strict DOM/ES2024 declaration check is separate from the older generated JS fixture. Follow [onboarding](GETTING-STARTED.md) for the existing-app, provider and optional-feature paths.
package/OVERLAYS.md ADDED
@@ -0,0 +1,52 @@
1
+ # Floating layers and focus
2
+
3
+ 0.5 adds shared dismissal, collision-aware placement and portals to Popover, DropdownMenu and Tooltip. Dialog remains a native modal/drawer. Contour uses MIT-licensed Floating UI DOM 1.8.0 for geometry and observers; it does not replace the public components with another component library.
4
+
5
+ ## Dismissal
6
+
7
+ One Escape reaches the deepest active layer; independent siblings use most-recent activation. Paint order follows that dismissal order. One outside primary-pointer press dismisses at most one layer. Presses in portaled descendants are inside their owned layer. Explicit buttons can still perform their own actions. A native modal backdrop does not also dismiss its modal when that press already belongs to a child layer.
8
+
9
+ Listeners attach to the layer's owner document, rebind on document moves and release on inactivity/unmount. They never stop event propagation. Handled Escape prevents its native default so a child dismissal does not also cancel its native parent dialog. Other keys and ordinary outside focus remain available. `onDismiss(reason, nativeEvent)` on Popover/Menu can call `preventDefault()` to veto dismissal. Dialog uses its existing `onCancel` and `onClick` cancellation contracts. `useDismiss(ref, active, callback, options)` uses the same registry; additional `branches` include a caller's trigger/portal. `layerElement` opts a custom floating element into owned stack order; its previous inline z-index is restored on release.
10
+
11
+ A closed ancestor suspends descendant layers without clearing their controlled/uncontrolled state. Reopening resumes that state; reset child state in the parent's application callback when a different policy is wanted. Modal/native close buttons and controlled parent updates are application-owned.
12
+
13
+ ## Placement and containers
14
+
15
+ Popover/Menu accept `side` (bottom by default), `align` (start by default; center/end supported), `sideOffset` (8 px) and `collisionPadding` (8 px). Tooltip retains `placement` (top), with center alignment and a 10 px offset. Alignment is logical in RTL. Placement flips, shifts on both axes and constrains scrollable content to the clipping boundary; scroll/resize/layout/element observers update it while open and clean up on close. Nonfinite offsets/padding fall back to defaults; negative collision padding clamps to zero. A geometry failure retains a viewport fallback and emits a warning rather than leaving an unhandled promise. Geometry properties and stack order are owned; use part props for appearance/content, not competing position/transform declarations. `--contour-z-overlay` configures the nonmodal stacking base (30); native modal top-layer ordering is browser-owned.
16
+
17
+ Default `portal` is true. Content renders in the trigger's owner-document body, or **inside its open native dialog**. A small portal wrapper carries inherited variables, font, text color and direction and follows ancestor theme/style mutations. This escapes a clipped or transformed theme root without changing the document's theme. `container` selects an explicit element in the same document; outside/wrong-document containers inside an active modal fall back to that modal. Keep custom containers unclipped and provide enough space. `portal={false}` preserves local DOM placement for a deliberate integration; clipping ancestors may then clip it.
18
+
19
+ **Migration from 0.4:** default portal DOM placement changes ancestor selectors and DOM-based tests. Target `panelProps.className` directly rather than `.host-root .host-panel`; use `portal={false}` when local placement is required. Root refs still target the wrapper, trigger refs target buttons and panel refs target panels, and may detach/reattach when the portal container changes. No deep import is needed.
20
+
21
+ ## Triggers and focus
22
+
23
+ `trigger` still supplies button content. `renderTrigger` supplies a supported ref-forwarding button component. Spread the provided props so refs, caller handlers and owned type/expanded/controls/haspopup relationships survive; do not nest a button inside the default trigger. An ordinary native button or Contour Button works. Link/non-button triggers are not supported by this contract.
24
+
25
+ ```tsx
26
+ import { Button, Popover, TextField } from "@abuzareal/contour-ui";
27
+ export function HelpPanel() {
28
+ return (
29
+ <Popover
30
+ title="Help"
31
+ trigger="Help"
32
+ renderTrigger={(props) => <Button {...props} variant="secondary" />}
33
+ >
34
+ <TextField label="Question" />
35
+ </Popover>
36
+ );
37
+ }
38
+ ```
39
+
40
+ Popover focuses its panel on entry; `initialFocus={ref}` selects a mounted descendant and `initialFocus={false}` keeps the trigger focused. Cancelable `onOpenAutoFocus` / `onCloseAutoFocus` events allow caller control. Escape returns to a connected trigger by default, including controlled dismissal requests. Outside press does not steal focus from its destination. Programmatic close/unmount returns focus when the layer or a descendant owned it. A missing/removed trigger receives no forced substitute; the application owns its workflow fallback. Popover is nonmodal: it does not trap focus or make the background inert. Portal DOM order affects Tab order; choose inline placement or explicit focus customization for an application-specific sequence.
41
+
42
+ Menu enters the first enabled action; ArrowUp from its trigger enters the last. ArrowUp/Down, Home/End and typeahead skip disabled entries. Selection/Escape return to the trigger; Tab dismisses without consuming Tab. Empty/all-disabled menus focus their panel. Labels/content are caller-owned. Dialog uses native focus confinement and focus return; its new `initialFocus` chooses a mounted modal descendant. `onOpenAutoFocus` can cancel that explicit override; it does not undo native showModal autofocus. Existing nested locks, drawer placement, long content and cleanup remain supported.
43
+
44
+ ## Supplemental hints
45
+
46
+ Tooltip content is a noninteractive string. Hover and keyboard focus cooperate; leaving the pointer does not erase a focused hint. Trigger-to-content travel stays available through a bounded pointer corridor; the hint remains while pointer/focus applies. Escape suppresses it until the current hover/focus conditions end and a new session begins. Existing `aria-describedby` values and the child's accessible name/ref/events are retained. Pointer content is hoverable; tooltip content is never a substitute for a visible label, essential instruction, validation error or interactive panel. Touch users must have the same essential information visibly available.
47
+
48
+ Production checks cover nested/sibling/portaled layers, native modal portals, touch outside press, RTL, narrow/resized viewports, long scrolling panels and CSS zoom. These are automated browser checks, not physical mobile-keyboard or assistive-technology certification. See [W3C hover/focus guidance](https://www.w3.org/WAI/WCAG21/Techniques/client-side-script/SCR39) and [Floating UI observer lifecycle](https://floating-ui.com/docs/autoupdate).
49
+
50
+ Keep a portal container stable while open. Changing its DOM target can remount the portaled subtree; keep unsaved child state in the owner when container changes are required.
51
+
52
+ Default-open children choose an enclosing native dialog even before its open attribute is set. When Dialog calls showModal in the same commit, these children enter its top layer without an additional click or render.