@abuzareal/contour-ui 0.5.0 → 0.6.1

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 (87) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/COMBOBOX.md +87 -0
  3. package/COMPOSITION.md +4 -0
  4. package/CONTRACTS.md +27 -27
  5. package/DOCS.md +86 -0
  6. package/FORMS.md +4 -0
  7. package/FOUNDATION.md +35 -0
  8. package/GETTING-STARTED.md +67 -0
  9. package/HELPERS.md +33 -0
  10. package/HOOKS.md +67 -0
  11. package/MANUAL-QA.md +38 -0
  12. package/MIGRATION.md +44 -0
  13. package/MODELS.md +18 -0
  14. package/NEXTJS.md +3 -1
  15. package/OVERLAYS.md +4 -0
  16. package/PROP-REFERENCE.md +37 -0
  17. package/PUBLIC-API.md +174 -164
  18. package/README.md +9 -5
  19. package/RECIPES.md +43 -0
  20. package/REFERENCE.md +193 -0
  21. package/RELEASING.md +8 -2
  22. package/SIZE.md +9 -3
  23. package/STYLING.md +4 -0
  24. package/SUPPORT.md +36 -0
  25. package/THREE.md +40 -0
  26. package/dist/components/ui/Combobox.d.ts +325 -0
  27. package/dist/components/ui/Combobox.js +135 -0
  28. package/dist/components/ui/Combobox.types.d.ts +46 -0
  29. package/dist/components/ui/Combobox.types.js +1 -0
  30. package/dist/components/ui/DataTable.js +5 -4
  31. package/dist/components/ui/Field.js +1 -1
  32. package/dist/components/ui/FloatingPortal.js +2 -40
  33. package/dist/components/ui/FloatingScope.d.ts +9 -0
  34. package/dist/components/ui/FloatingScope.js +43 -0
  35. package/dist/components/ui/Table.d.ts +20 -0
  36. package/dist/components/ui/Table.js +9 -0
  37. package/dist/hooks/useComboboxAnchor.d.ts +17 -0
  38. package/dist/hooks/useComboboxAnchor.js +61 -0
  39. package/dist/hooks/useInitialHashScroll.js +5 -1
  40. package/dist/hooks/usePrintPreparation.js +12 -1
  41. package/dist/hooks/useScrollSpy.js +6 -3
  42. package/dist/index.d.ts +2 -0
  43. package/dist/index.js +2 -0
  44. package/dist/kit/data-display.css +6 -0
  45. package/dist/kit/forms.css +34 -0
  46. package/dist/lib/componentClasses.js +1 -1
  47. package/dist/scoped/kit/data-display.css +6 -0
  48. package/dist/scoped/kit/forms.css +34 -0
  49. package/docs/prop-metadata.json +2668 -0
  50. package/examples/onboarding/SettingsForm.jsx +30 -0
  51. package/examples/onboarding/SettingsForm.tsx +50 -0
  52. package/examples/optional/LazyScene.tsx +78 -0
  53. package/examples/optional/OptionalSmoothScroll.tsx +25 -0
  54. package/examples/optional/SmoothScrollOwner.tsx +7 -0
  55. package/examples/optional/optional.css +25 -0
  56. package/examples/recipes/AsyncProjectPicker.tsx +82 -0
  57. package/examples/recipes/DeleteProjectConfirmation.tsx +97 -0
  58. package/examples/recipes/InputCompositions.tsx +49 -0
  59. package/examples/recipes/RemoteRecordsTable.tsx +256 -0
  60. package/examples/recipes/useRemoteResource.ts +81 -0
  61. package/examples/reference/HookExamples.tsx +146 -0
  62. package/examples/reproduction/README.md +9 -0
  63. package/examples/reproduction/index.html +14 -0
  64. package/examples/reproduction/package.json +27 -0
  65. package/examples/reproduction/src/main.tsx +33 -0
  66. package/examples/reproduction/src/style.css +19 -0
  67. package/examples/reproduction/tsconfig.json +18 -0
  68. package/examples/reproduction/vite.config.ts +19 -0
  69. package/examples/starters/next/README.md +18 -0
  70. package/examples/starters/next/app/Demo.tsx +65 -0
  71. package/examples/starters/next/app/layout.tsx +25 -0
  72. package/examples/starters/next/app/next-page/page.tsx +9 -0
  73. package/examples/starters/next/app/page.tsx +11 -0
  74. package/examples/starters/next/app/style.css +19 -0
  75. package/examples/starters/next/next-env.d.ts +2 -0
  76. package/examples/starters/next/next.config.mjs +1 -0
  77. package/examples/starters/next/package.json +27 -0
  78. package/examples/starters/next/proxy.ts +16 -0
  79. package/examples/starters/next/tsconfig.json +30 -0
  80. package/examples/starters/vite/README.md +16 -0
  81. package/examples/starters/vite/index.html +14 -0
  82. package/examples/starters/vite/package.json +27 -0
  83. package/examples/starters/vite/src/main.tsx +81 -0
  84. package/examples/starters/vite/src/style.css +19 -0
  85. package/examples/starters/vite/tsconfig.json +18 -0
  86. package/examples/starters/vite/vite.config.ts +19 -0
  87. package/package.json +37 -4
package/CHANGELOG.md CHANGED
@@ -6,6 +6,22 @@ Changes to the published `@abuzareal/contour-ui` package. Version headings refer
6
6
 
7
7
  No changes recorded.
8
8
 
9
+ ## 0.6.1
10
+
11
+ Compatible source corrections; publication is pending. Restore print theme/disclosures on repeated events or unmount, cancel delayed hash alignment after unmount, preserve delimiter-containing scroll-spy IDs, and close an open Combobox on Tab without cancelling native traversal or changing selection. Add hook/helper/optional-entry references, emitted prop/example drift checks, JavaScript onboarding and migration/history material. Correct the open Combobox background hiding with a labelled non-modal popup, keeping Base UI selection/navigation/positioning parts. Strict whole-document accessibility scans now pass in selected Chromium/WebKit fixtures; the unmodified upstream composition remains a separate failing diagnostic. Add visual QA without excluding axe rules. Manual screen-reader checks remain pending; no universal accessibility claim is made.
12
+
13
+ ## 0.6.0
14
+
15
+ Add allowed-value selection and public adoption material. This heading records source work, not npm publication.
16
+
17
+ - 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.
18
+ - 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.
19
+ - 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.
20
+ - 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.
21
+ - 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.
22
+
23
+ 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.
24
+
9
25
  ## 0.5.0
10
26
 
11
27
  Combined overlay, styling and size work. This heading records source work, not npm publication.
package/COMBOBOX.md ADDED
@@ -0,0 +1,87 @@
1
+ # Combobox — 0.6.1
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.1/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 correction in 0.6.1:** Contour uses a labelled non-modal popup region and listbox, retaining Base UI's public selection/navigation/positioning parts. Opening it no longer hides the surrounding page. Whole-document axe scans pass in the selected Chromium/WebKit fixtures with no excluded rules; Tab and Shift+Tab close it while preserving native traversal. The older Base UI Popup composition's failing diagnostic remains available separately. See [support evidence and limitations](SUPPORT.md); manual VoiceOver/NVDA review remains pending. These fixtures do not establish WCAG conformance.
86
+
87
+ [Report Combobox 0.6.1](https://github.com/abuzareal/contour-ui/issues/new?template=bug_report.yml&title=Combobox%200.6.1%3A%20&component=Combobox&version=0.6.1), using the [public minimal reproduction](examples/reproduction). Source links for this version resolve when its release tag is published.
package/COMPOSITION.md CHANGED
@@ -191,3 +191,7 @@ Anchors have no native disabled state. Render a suitable non-link explanation wh
191
191
  ## Abstraction review
192
192
 
193
193
  Each addition serves a bounded need: native focus/measurement/trigger refs; existing Dialog/Tabs/Popover root/part access; ID-based reorder-safe tab focus; caller handler cancellation. Private helpers implement those lifecycles. No runtime dependency, provider, build/distribution/styling system, generic component-replacement API or blanket polymorphism is introduced. Existing menu disabled items, keyboard opening, typeahead, stable identity and focus return remain covered.
194
+
195
+ ## Version and reproduction
196
+
197
+ Reference version **0.6.1** (candidate until published). [Versioned source](https://github.com/abuzareal/contour-ui/tree/v0.6.1/src) and [emitted type inventory](PUBLIC-API.md) describe this contract; tag links resolve after release. [Report Composition 0.6.1](https://github.com/abuzareal/contour-ui/issues/new?template=bug_report.yml&component=Composition&version=0.6.1), using the [public minimal reproduction](examples/reproduction) and synthetic data.
package/CONTRACTS.md CHANGED
@@ -1,16 +1,16 @@
1
1
  # Architecture, scope and compatibility
2
2
 
3
- Contract version **0.5.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.1**, 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,29 +30,29 @@ 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
 
44
44
  ## Environment and validation policy
45
45
 
46
- | Environment | Declared or exercised scope | Limits |
47
- | ----------------------- | ------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
48
- | React/ReactDOM | Peer range above; packed matrix exercises 18.3.1, 19.0.0 and 19.3.0 | Matching pairs required. Other allowed patches are not individually certified. |
49
- | TypeScript | Strict source/fixtures; pinned optional/Next fixtures use 5.9.3; React 18 and 19 declarations compiled | No universal older/newer compiler support claim; JS users need no compiler at runtime. |
50
- | Node | Manifest >=22 for installation/tooling; use 22.12+ or 24+ with current Vite tooling | Browser components do not need Node in the browser. Actual runner versions are recorded per check. |
51
- | ESM/CSS-capable bundler | Packed Vite consumers exercise imports/declarations/CSS/fonts with no optional peers | No CommonJS build or unbundled-browser CSS loader promised. |
52
- | Next.js | Next 16.4.0 / React and ReactDOM 19.3.0, App Router, webpack production build | [NEXTJS.md](NEXTJS.md); other versions, Turbopack, Pages Router and Next Lenis remain unverified. |
53
- | Optional Three | Separate consumers and lazy Next mode; explicit Three/types, WebGL, sized host and static fallback | Experimental API; sampled lifecycle checks do not validate every custom scene/GPU/device. |
54
- | Optional scroll | Separate declaration/build consumer with Lenis and one scroll owner | Caller handles pause/reduced motion; generic Next Lenis integration unverified. |
55
- | Browsers | Playwright Chromium exercises selected native-dialog/reset/graphics/hydration/catalogue journeys | Emulation is not a physical device. Chromium/axe is not Firefox/WebKit/Safari or manual VoiceOver/NVDA verification. |
46
+ | Environment | Declared or exercised scope | Limits |
47
+ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
48
+ | React/ReactDOM | Peer range above; packed matrix exercises 18.3.1, 19.0.0 and 19.3.0 | Matching pairs required. Other allowed patches are not individually certified. |
49
+ | TypeScript | Strict source/fixtures; optional/existing Next fixtures use 5.9.3; standalone current Next starter uses 7.0.2; React 18 and 19 declarations compiled | No universal older/newer compiler support claim; JS users need no compiler at runtime. |
50
+ | Node | Manifest >=22 for installation/tooling; use 22.12+ or 24+ with current Vite tooling | Browser components do not need Node in the browser. Actual runner versions are recorded per check. |
51
+ | ESM/CSS-capable bundler | Packed Vite consumers exercise imports/declarations/CSS/fonts with no optional peers | No CommonJS build or unbundled-browser CSS loader promised. |
52
+ | Next.js | Next 16.4.0 / React and ReactDOM 19.3.0, App Router, webpack production build | [NEXTJS.md](NEXTJS.md); other versions, Turbopack, Pages Router and Next Lenis remain unverified. |
53
+ | Optional Three | Separate consumers and lazy Next mode; explicit Three/types, WebGL, sized host and static fallback | Experimental API; sampled lifecycle checks do not validate every custom scene/GPU/device. |
54
+ | Optional scroll | Separate declaration/build consumer with Lenis and one scroll owner | Caller handles pause/reduced motion; generic Next Lenis integration unverified. |
55
+ | Browsers | Selected Chromium and WebKit production fixtures; exact results and known failures in SUPPORT.md | Emulation is not a physical device. Engine emulation is not physical Safari or manual VoiceOver/NVDA; local Firefox launch is blocked. |
56
56
 
57
57
  Release gates use one exact archive: types/lint/build, Node/component tests, native-dialog/graphics browser checks, exports/archive inspection, React matrix, optional consumers, pinned Next core/lazy-Three modes and dependency audit. Catalogue has separate required checks/affected journeys. Record checksum, actual environments and passed/failed/skipped/blocked results. Historical results are dated evidence, not a fresh pass; see [RELEASING.md](RELEASING.md).
58
58
 
@@ -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
 
package/DOCS.md ADDED
@@ -0,0 +1,86 @@
1
+ # Public documentation contract and coverage
2
+
3
+ For Contour 0.6.1, 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, full website prop-page generation and versioned website archives remain separate work. A bounded emitted-declaration prototype and drift gate now live in PROP-REFERENCE.md and docs/prop-metadata.json; REFERENCE.md maps all symbols and style/data paths. 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.
81
+
82
+ ## Library reference work
83
+
84
+ [HOOKS.md](HOOKS.md), [HELPERS.md](HELPERS.md) and [THREE.md](THREE.md) document non-component contracts and lifecycle ownership. [PROP-REFERENCE.md](PROP-REFERENCE.md) audits the nine historical guides and explains generated versus authored metadata. [MIGRATION.md](MIGRATION.md) preserves version-specific contracts and upgrade/rollback steps; [MODELS.md](MODELS.md) distinguishes package/source/company documentation models. [SUPPORT.md](SUPPORT.md) records automated/manual results and failures. Full PAGE website promotion remains separate.
85
+
86
+ JavaScript and TypeScript onboarding use the same source contract: scripts/build-onboarding.mjs strips types into the shipped JSX example and check:onboarding detects drift. The examples are copied from the actual archive for strict TS/build checks and exercised as independent browser journeys. No private workspace guidance is required to consume them.
package/FORMS.md CHANGED
@@ -92,3 +92,7 @@ Native TextField registration forwards name/onChange/onBlur/ref. RHF submits its
92
92
  The recipe uses useForm defaultValues, RHF field validation/server errors, handleSubmit, controller refs and reset. Its reset command performs native form.reset() first so registered field counters/auto-resize observe the reset, then reset(defaults) updates RHF state. For arbitrary programmatic RHF setValue calls on fields with counters/auto-resize, use Controller and pass the controlled value; direct DOM writes through a registered ref do not dispatch native input events. Its shared submission lifecycle ignores late results after reset/cancel/unmount even if the service disregards AbortSignal. Real services must enforce validation, authentication, authorization, CSRF policy and idempotency/overlap handling at the server boundary. Treat parsed responses as unknown until your adapter validates them; TypeScript alone does not validate network data.
93
93
 
94
94
  References: [React native input ownership](https://react.dev/reference/react-dom/components/input), [native range semantics](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/range), [RHF Controller](https://react-hook-form.com/docs/usecontroller/controller), [RHF reset](https://react-hook-form.com/docs/useform/reset), [Radix Switch form contract](https://www.radix-ui.com/primitives/docs/components/switch). These inform bounded contracts; Contour does not add a second interaction foundation or copy their implementation. Native-form checks are scoped Chromium evidence, not cross-browser/screen-reader certification.
95
+
96
+ ## Version and reproduction
97
+
98
+ Reference version **0.6.1** (candidate until published). [Versioned source](https://github.com/abuzareal/contour-ui/tree/v0.6.1/src) and [emitted type inventory](PUBLIC-API.md) describe this contract; tag links resolve after release. [Report Forms 0.6.1](https://github.com/abuzareal/contour-ui/issues/new?template=bug_report.yml&component=Forms&version=0.6.1), using the [public minimal reproduction](examples/reproduction) and synthetic data.
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
+ In 0.6.1, Contour replaces the Base UI Popup wrapper with its own labelled non-modal region, composed around the public Positioner/List/Item parts. This corrects the observed input-outside-popup background hiding without changing the selection/form contract, removing ARIA or patching private internals. The listbox uses the visible field label; closed popup content is hidden. Contour completes the public Root actions close lifecycle after hiding its non-animated popup so controlled query text returns to the selected label. Whole-document strict axe and keyboard checks pass in selected Chromium/WebKit fixtures. The original unmodified upstream composition remains a separate strict failing diagnostic, documented in SUPPORT.md; manual AT review remains pending. The adapter continues to use public virtual-anchor and collision-Rect extension points to keep fixed-position coordinates correct under inherited CSS zoom.
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,67 @@
1
+ # Adopt Contour — 0.6.1
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.1
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.1%3A%20&component=Integration&version=0.6.1) contains version/component, not private data. Report only synthetic records and public reproducible source. [Documentation coverage and limits](DOCS.md).
64
+
65
+ ## JavaScript and TypeScript examples
66
+
67
+ Copy [SettingsForm.jsx](examples/onboarding/SettingsForm.jsx) for a JavaScript React app or [SettingsForm.tsx](examples/onboarding/SettingsForm.tsx) for TypeScript. Render SettingsForm after importing scoped.css; both inspect the same native FormData locally, reset defaults/output and focus the native name input. Neither saves data or needs a provider. The JSX is generated by stripping types from the TSX source, not a second hand-maintained example; it contains no TypeScript syntax. Bring your own React entry/router and server validation. See MIGRATION.md for upgrades and older contracts.
package/HELPERS.md ADDED
@@ -0,0 +1,33 @@
1
+ # Helpers and public types
2
+
3
+ Use direct imports from `@abuzareal/contour-ui`. [PUBLIC-API.md](PUBLIC-API.md) links exact release source/types; [REFERENCE.md](REFERENCE.md) checks complete export coverage. Helpers do not require a provider. Only the pure operations below are server-call safe; importing the module and invoking a browser operation are different contracts.
4
+
5
+ | Helper | Inputs/defaults and result | Boundaries |
6
+ | ----------------- | ------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
7
+ | cx | Variadic string/false/null/undefined; joins truthy parts with spaces. | No class parsing, deduplication, token validation or prefixing. Use for arbitrary consumer strings/IDs only when joining is appropriate. |
8
+ | ccx | Same inputs; deduplicates whitespace-separated names and appends contour-prefixed equivalents for known library classes. | Compatibility class helper, not an ID/ARIA builder. Unknown consumer classes remain unchanged. |
9
+ | buttonClassName | variant="primary", size="md", optional className; returns compatibility/prefixed button classes. | ButtonVariant: primary/accent/secondary/ghost/danger; ButtonSize: sm/md/lg. Styling an anchor does not give it button semantics. |
10
+ | formatIndex | number; String(value).padStart(2,"0"). | 3 → "03", 12 → "12". It does not round, add one, validate finite values, format a locale or normalize negative/fractional inputs. Pass an already chosen index. |
11
+ | fieldDescribedBy | id, optional hint/error; error → id-error, else hint → id-hint, else undefined. | Matches Field's message priority. Does not render an element or preserve arbitrary caller description IDs; callers merge those separately. |
12
+ | nextIndexForKey | key/current/count, orientation="horizontal", direction="ltr"; wrapped index or null. | count<=0 and unrelated keys return null. Home/End choose extremes. RTL reverses horizontal arrows. It does not skip disabled items, focus DOM nodes or validate every numeric input. Caller owns a valid current index/count and disabled-item policy. |
13
+ | safeHref | string or undefined; returns allowed href unchanged or undefined. | Rejects executable/disallowed schemes, including whitespace-obfuscated forms. Relative/hash/protocol-relative and permitted https/http/mailto/tel links are not a trusted-destination allowlist. Validate destination/origin and authorize redirects separately. |
14
+ | storageKeys | openingSeen="aa-opening-seen", theme="aa-theme". | Retained legacy origin-wide keys; not namespaced per application/scope. |
15
+ | readStorage | area="local" or "session", key; string or null. | Browser storage access wrapped in try/catch; blocked/missing/SSR access returns null. It does not parse/validate JSON or distinguish missing from denied. |
16
+ | writeStorage | area/key/string value; boolean success. | False for inaccessible/throwing storage. It does not encrypt data, provide a schema, broadcast an app event or write a server cookie. |
17
+ | motionDisabledFor | Element; boolean. | Browser-only synchronous preference/ancestor check; see HOOKS.md. |
18
+
19
+ ## Public types
20
+
21
+ Section is `{id: string; label: string; title: string; number: number}`. It describes repeated page metadata, not a router, validator or React element. The application supplies stable IDs and valid numbers. CopyStatus, Theme, DismissReason/Options, PanelFocusOptions, FloatingOptions, ToastInput and ToastContextValue are described with their hooks in HOOKS.md. Component Props/item/variant aliases remain in the emitted declarations; they are types, not additional component exports. Generic TableColumn and TabItem must be read with their associated component contracts.
22
+
23
+ Use `import type` for type-only values. Do not deep-import dist files or assume a type-named symbol exists at runtime. Native inherited React props and overloads are retained by the declarations; generated metadata is a discovery aid and does not replace them. No arbitrary polymorphic component or enterprise grid contract is implied by these helpers.
24
+
25
+ ## Data and URL responsibility
26
+
27
+ Read parsed storage/network data as unknown. Validate structures, elements, finite numbers/ranges and IDs before supplying components. A successful storage write is not server persistence. A safe URL scheme is not proof of a safe destination, a permitted server redirect or sanitized remote HTML. React escaping does not authorize data access; server validation and permissions remain at the relevant boundary.
28
+
29
+ The JavaScript onboarding example in [GETTING-STARTED.md](GETTING-STARTED.md) uses the same native form payload contract without requiring TypeScript syntax. Report synthetic reproductions and exact package/browser versions; never include private storage contents or credentials.
30
+
31
+ ## Version and reproduction
32
+
33
+ Reference version **0.6.1** (candidate until published). [Versioned source](https://github.com/abuzareal/contour-ui/tree/v0.6.1/src) and [emitted type inventory](PUBLIC-API.md) describe this contract; tag links resolve after release. [Report Helpers 0.6.1](https://github.com/abuzareal/contour-ui/issues/new?template=bug_report.yml&component=Helpers&version=0.6.1), using the [public minimal reproduction](examples/reproduction) and synthetic data.