@capillaryjs/capillary-ui 1.0.0-alpha.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 (178) hide show
  1. package/CHANGELOG.md +459 -0
  2. package/LICENSE +55 -0
  3. package/NOTICE +2 -0
  4. package/README.md +853 -0
  5. package/colors/README.md +63 -0
  6. package/colors/gray/colors.css +16 -0
  7. package/colors/green/colors.css +16 -0
  8. package/colors/iceblue/colors.css +19 -0
  9. package/colors/ocean/colors.css +16 -0
  10. package/colors/orange/colors.css +16 -0
  11. package/colors/purple/colors.css +16 -0
  12. package/colors/red/colors.css +16 -0
  13. package/colors/yellow/colors.css +16 -0
  14. package/dist/Components/Placeholder.d.ts +15 -0
  15. package/dist/Components/Placeholder.d.ts.map +1 -0
  16. package/dist/Components/app/app.d.ts +41 -0
  17. package/dist/Components/app/app.d.ts.map +1 -0
  18. package/dist/Components/component.d.ts +256 -0
  19. package/dist/Components/component.d.ts.map +1 -0
  20. package/dist/Components/controlUtils.d.ts +23 -0
  21. package/dist/Components/controlUtils.d.ts.map +1 -0
  22. package/dist/Components/data/descriptionList.d.ts +23 -0
  23. package/dist/Components/data/descriptionList.d.ts.map +1 -0
  24. package/dist/Components/data/filterState.d.ts +38 -0
  25. package/dist/Components/data/filterState.d.ts.map +1 -0
  26. package/dist/Components/data/infoPanel.d.ts +29 -0
  27. package/dist/Components/data/infoPanel.d.ts.map +1 -0
  28. package/dist/Components/data/listview/listview.d.ts +51 -0
  29. package/dist/Components/data/listview/listview.d.ts.map +1 -0
  30. package/dist/Components/data/selectionhandler.d.ts +87 -0
  31. package/dist/Components/data/selectionhandler.d.ts.map +1 -0
  32. package/dist/Components/data/table/DataTable.d.ts +79 -0
  33. package/dist/Components/data/table/DataTable.d.ts.map +1 -0
  34. package/dist/Components/data/table/FilterPanel.d.ts +41 -0
  35. package/dist/Components/data/table/FilterPanel.d.ts.map +1 -0
  36. package/dist/Components/data/table/TableHeader.d.ts +23 -0
  37. package/dist/Components/data/table/TableHeader.d.ts.map +1 -0
  38. package/dist/Components/data/table/TableHeaderCell.d.ts +41 -0
  39. package/dist/Components/data/table/TableHeaderCell.d.ts.map +1 -0
  40. package/dist/Components/data/table/tableDataSource.d.ts +49 -0
  41. package/dist/Components/data/table/tableDataSource.d.ts.map +1 -0
  42. package/dist/Components/data/table/tableQuery.d.ts +29 -0
  43. package/dist/Components/data/table/tableQuery.d.ts.map +1 -0
  44. package/dist/Components/data/treeview/treeModel.d.ts +23 -0
  45. package/dist/Components/data/treeview/treeModel.d.ts.map +1 -0
  46. package/dist/Components/data/treeview/treeitem.d.ts +21 -0
  47. package/dist/Components/data/treeview/treeitem.d.ts.map +1 -0
  48. package/dist/Components/data/treeview/treeview.d.ts +49 -0
  49. package/dist/Components/data/treeview/treeview.d.ts.map +1 -0
  50. package/dist/Components/dialog/dialog.d.ts +46 -0
  51. package/dist/Components/dialog/dialog.d.ts.map +1 -0
  52. package/dist/Components/layout/declarativeRegion.d.ts +30 -0
  53. package/dist/Components/layout/declarativeRegion.d.ts.map +1 -0
  54. package/dist/Components/layout/groupBox.d.ts +21 -0
  55. package/dist/Components/layout/groupBox.d.ts.map +1 -0
  56. package/dist/Components/layout/header.d.ts +20 -0
  57. package/dist/Components/layout/header.d.ts.map +1 -0
  58. package/dist/Components/layout/layout.d.ts +28 -0
  59. package/dist/Components/layout/layout.d.ts.map +1 -0
  60. package/dist/Components/layout/layoutTraits.d.ts +39 -0
  61. package/dist/Components/layout/layoutTraits.d.ts.map +1 -0
  62. package/dist/Components/layout/optionGroup.d.ts +35 -0
  63. package/dist/Components/layout/optionGroup.d.ts.map +1 -0
  64. package/dist/Components/layout/optionsBox.d.ts +12 -0
  65. package/dist/Components/layout/optionsBox.d.ts.map +1 -0
  66. package/dist/Components/layout/panel.d.ts +34 -0
  67. package/dist/Components/layout/panel.d.ts.map +1 -0
  68. package/dist/Components/layout/routedSelection.d.ts +10 -0
  69. package/dist/Components/layout/routedSelection.d.ts.map +1 -0
  70. package/dist/Components/layout/sidebar.d.ts +27 -0
  71. package/dist/Components/layout/sidebar.d.ts.map +1 -0
  72. package/dist/Components/layout/splitView.d.ts +82 -0
  73. package/dist/Components/layout/splitView.d.ts.map +1 -0
  74. package/dist/Components/layout/tabpanel/tab.d.ts +15 -0
  75. package/dist/Components/layout/tabpanel/tab.d.ts.map +1 -0
  76. package/dist/Components/layout/tabpanel/tabline.d.ts +31 -0
  77. package/dist/Components/layout/tabpanel/tabline.d.ts.map +1 -0
  78. package/dist/Components/layout/tabpanel/tabpanel.d.ts +44 -0
  79. package/dist/Components/layout/tabpanel/tabpanel.d.ts.map +1 -0
  80. package/dist/Components/lineinputs/CheckableControl.d.ts +7 -0
  81. package/dist/Components/lineinputs/CheckableControl.d.ts.map +1 -0
  82. package/dist/Components/lineinputs/LabeledInputControl.d.ts +7 -0
  83. package/dist/Components/lineinputs/LabeledInputControl.d.ts.map +1 -0
  84. package/dist/Components/lineinputs/SelectControl.d.ts +7 -0
  85. package/dist/Components/lineinputs/SelectControl.d.ts.map +1 -0
  86. package/dist/Components/lineinputs/checkbox/Checkbox.d.ts +46 -0
  87. package/dist/Components/lineinputs/checkbox/Checkbox.d.ts.map +1 -0
  88. package/dist/Components/lineinputs/checkbox/QuadCheckbox.d.ts +11 -0
  89. package/dist/Components/lineinputs/checkbox/QuadCheckbox.d.ts.map +1 -0
  90. package/dist/Components/lineinputs/checkbox/TriCheckbox.d.ts +11 -0
  91. package/dist/Components/lineinputs/checkbox/TriCheckbox.d.ts.map +1 -0
  92. package/dist/Components/lineinputs/datetime/Calendar.d.ts +23 -0
  93. package/dist/Components/lineinputs/datetime/Calendar.d.ts.map +1 -0
  94. package/dist/Components/lineinputs/datetime/DatePicker.d.ts +62 -0
  95. package/dist/Components/lineinputs/datetime/DatePicker.d.ts.map +1 -0
  96. package/dist/Components/lineinputs/datetime/DateTimePicker.d.ts +52 -0
  97. package/dist/Components/lineinputs/datetime/DateTimePicker.d.ts.map +1 -0
  98. package/dist/Components/lineinputs/datetime/TimePicker.d.ts +46 -0
  99. package/dist/Components/lineinputs/datetime/TimePicker.d.ts.map +1 -0
  100. package/dist/Components/lineinputs/datetime/civilDate.d.ts +37 -0
  101. package/dist/Components/lineinputs/datetime/civilDate.d.ts.map +1 -0
  102. package/dist/Components/lineinputs/datetime/timeString.d.ts +26 -0
  103. package/dist/Components/lineinputs/datetime/timeString.d.ts.map +1 -0
  104. package/dist/Components/lineinputs/dropdown.d.ts +49 -0
  105. package/dist/Components/lineinputs/dropdown.d.ts.map +1 -0
  106. package/dist/Components/lineinputs/label.d.ts +14 -0
  107. package/dist/Components/lineinputs/label.d.ts.map +1 -0
  108. package/dist/Components/lineinputs/radio.d.ts +69 -0
  109. package/dist/Components/lineinputs/radio.d.ts.map +1 -0
  110. package/dist/Components/lineinputs/textbox.d.ts +40 -0
  111. package/dist/Components/lineinputs/textbox.d.ts.map +1 -0
  112. package/dist/Components/lineinputs/toggle.d.ts +40 -0
  113. package/dist/Components/lineinputs/toggle.d.ts.map +1 -0
  114. package/dist/Components/menu/button.d.ts +31 -0
  115. package/dist/Components/menu/button.d.ts.map +1 -0
  116. package/dist/Components/menu/toolbar.d.ts +15 -0
  117. package/dist/Components/menu/toolbar.d.ts.map +1 -0
  118. package/dist/Components/navigation/breadcrumb.d.ts +31 -0
  119. package/dist/Components/navigation/breadcrumb.d.ts.map +1 -0
  120. package/dist/Components/navigation/navigationBar.d.ts +53 -0
  121. package/dist/Components/navigation/navigationBar.d.ts.map +1 -0
  122. package/dist/Components/status/progressBar.d.ts +23 -0
  123. package/dist/Components/status/progressBar.d.ts.map +1 -0
  124. package/dist/Components/status/statusPresentation.d.ts +21 -0
  125. package/dist/Components/status/statusPresentation.d.ts.map +1 -0
  126. package/dist/Components/theme/stylesheetPicker.d.ts +42 -0
  127. package/dist/Components/theme/stylesheetPicker.d.ts.map +1 -0
  128. package/dist/index.d.ts +68 -0
  129. package/dist/index.d.ts.map +1 -0
  130. package/dist/index.js +7736 -0
  131. package/dist/index.js.map +1 -0
  132. package/dist/jsx-dev-runtime-BAF7C1E1.js +1368 -0
  133. package/dist/jsx-dev-runtime-BAF7C1E1.js.map +1 -0
  134. package/dist/jsx-dev-runtime.d.ts +2 -0
  135. package/dist/jsx-dev-runtime.d.ts.map +1 -0
  136. package/dist/jsx-dev-runtime.js +6 -0
  137. package/dist/jsx-dev-runtime.js.map +1 -0
  138. package/dist/jsx-runtime.d.ts +26 -0
  139. package/dist/jsx-runtime.d.ts.map +1 -0
  140. package/dist/jsx-runtime.js +9 -0
  141. package/dist/jsx-runtime.js.map +1 -0
  142. package/dist/localization.d.ts +76 -0
  143. package/dist/localization.d.ts.map +1 -0
  144. package/dist/routing/RouteLink.d.ts +18 -0
  145. package/dist/routing/RouteLink.d.ts.map +1 -0
  146. package/dist/routing/RouteOutlet.d.ts +38 -0
  147. package/dist/routing/RouteOutlet.d.ts.map +1 -0
  148. package/dist/routing/RouteQuery.d.ts +19 -0
  149. package/dist/routing/RouteQuery.d.ts.map +1 -0
  150. package/dist/routing/RouteScope.d.ts +18 -0
  151. package/dist/routing/RouteScope.d.ts.map +1 -0
  152. package/dist/routing/RouteValue.d.ts +19 -0
  153. package/dist/routing/RouteValue.d.ts.map +1 -0
  154. package/dist/routing/navigationAdapter.d.ts +33 -0
  155. package/dist/routing/navigationAdapter.d.ts.map +1 -0
  156. package/dist/routing/route.d.ts +64 -0
  157. package/dist/routing/route.d.ts.map +1 -0
  158. package/dist/routing/router.d.ts +109 -0
  159. package/dist/routing/router.d.ts.map +1 -0
  160. package/dist/runtime.d.ts +34 -0
  161. package/dist/runtime.d.ts.map +1 -0
  162. package/dist/services.d.ts +45 -0
  163. package/dist/services.d.ts.map +1 -0
  164. package/dist/styling/styleRegistry.d.ts +15 -0
  165. package/dist/styling/styleRegistry.d.ts.map +1 -0
  166. package/dist/styling/theme.d.ts +39 -0
  167. package/dist/styling/theme.d.ts.map +1 -0
  168. package/dist/util/filterMode.d.ts +15 -0
  169. package/dist/util/filterMode.d.ts.map +1 -0
  170. package/docs/application-composition-guide.md +843 -0
  171. package/docs/application-layout-guide.md +553 -0
  172. package/package.json +90 -0
  173. package/styles/structural.css +2388 -0
  174. package/themes/README.md +99 -0
  175. package/themes/base.css +344 -0
  176. package/themes/java/theme.css +21 -0
  177. package/themes/minimal/theme.css +7 -0
  178. package/themes/shiny/theme.css +168 -0
@@ -0,0 +1,843 @@
1
+ # Capillary UI Application Composition Guide
2
+
3
+ This guide helps you turn an application's intended functionality into readable
4
+ screens, component boundaries, and layouts. It assumes familiarity with HTML,
5
+ CSS, and TypeScript, but no particular experience with Capillary UI.
6
+
7
+ Start with the work the user needs to do. Decide which information and controls
8
+ belong together, which regions persist during navigation, and what should
9
+ happen when data changes. Then choose components and layout mechanics that
10
+ express those decisions.
11
+
12
+ These are design recommendations within Capillary UI's existing contracts, rather
13
+ than a mandatory application template. Applications own composition and policy;
14
+ Capillary UI owns presentation and browser lifecycle; Capillary owns reactive propagation.
15
+ The companion [repository layout guide](application-layout-guide.md) explains
16
+ where to put the resulting code.
17
+
18
+ ## 1. Start with workflows and relationships
19
+
20
+ For each screen, write down the question it answers and the actions it supports.
21
+ For example, a records application might have these workflows:
22
+
23
+ | Workflow | Information and interaction | Likely composition |
24
+ | --- | --- | --- |
25
+ | Find records needing attention | Search, status filters, matching records | Controls beside results |
26
+ | Investigate one record | Record selection, summary, history | Navigator beside a workspace with local tabs |
27
+ | Read a report | Headings, prose, supporting tables | A document that grows with its content |
28
+ | Monitor activity | Several changing result streams | Independently scrolling regions |
29
+
30
+ Similar colors, spacing, and headers do not require identical screen structure.
31
+ Give each workflow an appropriate arrangement while sharing presentation traits
32
+ and components where their meaning stays consistent.
33
+
34
+ Identify the authoritative values and how interactions affect them. A search
35
+ control can write a Capillary emitter used as a query argument; a results component
36
+ can observe the query result. Both components participate in one workflow
37
+ without either needing to know the other's DOM structure. Keep business rules
38
+ and reusable calculations in application/domain code, and use a view-owned
39
+ coordinator when several controls and results need orchestration. A simple
40
+ screen does not need a coordinator merely for consistency with larger screens.
41
+
42
+ Keep these boundaries distinct:
43
+
44
+ | Boundary | Decision it expresses |
45
+ | --- | --- |
46
+ | Component | A presentation or interaction responsibility with a useful contract |
47
+ | Route or tab | A navigable choice and its content lifetime |
48
+ | State owner | Who creates, changes, and disposes a value or operation |
49
+ | Layout region | How space is allocated and content arranged |
50
+ | Scroll container | Which content moves when the user scrolls |
51
+ | Island | A meaningful, visually self-contained work surface |
52
+
53
+ A results island can contain several components that share a view's state and
54
+ use one inner scroll container. None of those boundaries requires the others
55
+ to occupy the same place in the tree.
56
+
57
+ ### Choose an intentional layout boundary
58
+
59
+ Use the smallest component whose contract explains why the container exists:
60
+
61
+ | Component | Use it when | Do not use it merely for |
62
+ | --- | --- | --- |
63
+ | `Layout` | Children need a horizontal or vertical arrangement, allocation, or an explicit scroll owner, but the container has no further user-facing meaning | Surface chrome, a labelled region, or resizing |
64
+ | `Panel` | The region is a deliberate themed surface, optionally with a heading and toolbar; its body is a Layout | A neutral wrapper whose only job is child arrangement |
65
+ | `SplitView` | Exactly two named panes need user-controlled resizing | An ordinary two-column or two-row arrangement |
66
+
67
+ `SplitView` supplies the accessible separator and its interaction. `Layout`
68
+ and `Panel` do not; use a horizontal or vertical `Layout` for a fixed
69
+ arrangement. `Panel` is itself composed over an inner Layout, so its ordinary
70
+ children receive the same allocation and arrangement contract while the Panel
71
+ owns the surrounding chrome.
72
+
73
+ Do not add an authored `<div>` or other anonymous element merely to carry
74
+ layout classes. It has neither a Capillary UI component contract nor component-owned
75
+ structural and theme CSS, and it obscures whether the wrapper is neutral,
76
+ surface-like, or interactive. Use `Layout` for that neutral case. This is a
77
+ preference for intentional boundaries, not a ban on native HTML: use `main`,
78
+ `section`, `aside`, `nav`, `header`, `footer`, `article`, lists, tables, and
79
+ form elements when they express real document or control semantics. Such
80
+ application-owned native elements may still use Capillary UI's public traits when the
81
+ native semantic boundary is the right layout boundary.
82
+
83
+ For the three layout components, use direct `horizontal` / `vertical` and
84
+ `scroll` modifiers. Keep `allocation` named because its meaning is relative to
85
+ the parent's main axis. The public traits remain available for semantic native
86
+ elements and deliberate integration seams; they are not the default way to
87
+ invent generic wrappers.
88
+
89
+ ## 2. Separate persistent structure from changing content
90
+
91
+ Put application-wide branding, navigation, and status in the application root.
92
+ Put an outlet where page content changes. A page then declares the arrangement
93
+ needed for its own workflow.
94
+
95
+ The following excerpts use current Capillary UI APIs. Imports and application-specific
96
+ data implementations are omitted. `RecordsView`, `ActivityView`,
97
+ `RecordNavigator`, `RecordSummary`, `RecordHistory`, `RecordFilters`, and
98
+ `RecordsResults` are application components, not Capillary UI exports. See
99
+ [TSX setup](../README.md#set-up-tsx) for imports, presentation assets, and style
100
+ collection. Class components declare their rendered component dependencies so
101
+ `mountCapillaryUiApp()` can collect structural CSS, including components appearing only
102
+ in inactive routes or conditional branches.
103
+
104
+ ```tsx
105
+ const recordsRoute = defineRoute('records')
106
+ const activityRoute = defineRoute('activity')
107
+
108
+ class RecordsApp extends CapillaryUiApp {
109
+ protected override renderContent() {
110
+ return <>
111
+ <header className="cap-size-natural">
112
+ <h1>Records</h1>
113
+ </header>
114
+ <NavigationBar
115
+ allocation="natural"
116
+ label="Application sections"
117
+ items={[
118
+ {id: 'records', label: 'Records', to: routeTarget(recordsRoute)},
119
+ {id: 'activity', label: 'Activity', to: routeTarget(activityRoute)},
120
+ ]}
121
+ />
122
+ <main className="cap-size-flexible cap-layout-vertical">
123
+ <RouteOutlet
124
+ mountPolicy="active-only"
125
+ views={[
126
+ {id: 'records', route: recordsRoute, content: <RecordsView />},
127
+ {id: 'activity', route: activityRoute, content: <ActivityView />},
128
+ ]}
129
+ />
130
+ </main>
131
+ <footer className="cap-size-natural">Connected</footer>
132
+ </>
133
+ }
134
+
135
+ static dependencies = [NavigationBar, RouteOutlet, RecordsView, ActivityView]
136
+ }
137
+
138
+ const router = createBrowserRouter({adapter: createHashNavigation()})
139
+ const runtime = createCapillaryUiRuntime({router})
140
+ const app = mountCapillaryUiApp(runtime, RecordsApp, document.querySelector('#app')!, {
141
+ sizing: 'viewport',
142
+ layout: 'vertical',
143
+ landmark: 'none',
144
+ })
145
+ ```
146
+
147
+ Here `CapillaryUiApp` supplies the bounded root and its vertical arrangement. The
148
+ explicit `<main>` supplies the primary-content landmark, so the root uses
149
+ `landmark: 'none'` to avoid nesting main landmarks. The example assumes a
150
+ dedicated app document with its default body margin removed. At application
151
+ shutdown, destroy `app`, dispose the caller-owned `router`, and dispose any
152
+ application-owned service scope after its component tree.
153
+
154
+ `NavigationBar` renders native route links and their active state. `RouteOutlet`
155
+ owns the destination content and registers its sibling routes. Keep one owner
156
+ for that route set. Navigation does not require rebuilding the application
157
+ root or duplicating the header in each page.
158
+
159
+ ### Adapt organization localization at the root
160
+
161
+ When the application has selected a locale and loaded its ordinary catalogs,
162
+ adapt only Capillary UI-authored messages into the same runtime composition:
163
+
164
+ ```tsx
165
+ const capillaryUiMessages: CapillaryUiMessageOverrides = {
166
+ toolbarLabel: i18n.t('capillaryUi.toolbar.label'),
167
+ dataTableEmpty: i18n.t('capillaryUi.table.empty'),
168
+ tableSortColumnLabel: (label) => i18n.t('capillaryUi.table.sort', {label}),
169
+ }
170
+
171
+ document.documentElement.lang = i18n.locale
172
+
173
+ const runtime = createCapillaryUiRuntime({
174
+ router,
175
+ localization: {locale: i18n.locale, messages: capillaryUiMessages},
176
+ })
177
+ ```
178
+
179
+ Keep screen headings, navigation items, field labels, validation text, and
180
+ domain messages in the application's catalogs and pass their resolved values
181
+ as ordinary props/content. Capillary UI applies English fallback to omitted internal
182
+ keys and uses the configured locale for calendar display names and numerals. It does not load the
183
+ catalog, change `lang`/`dir`, or switch the runtime's locale reactively. If the
184
+ application changes language in place, recreate its Capillary UI runtime tree with a
185
+ new static localization snapshot. The application also owns direction and RTL
186
+ policy.
187
+
188
+ The header remains mounted; it can still update a title or status. Remaining
189
+ mounted also differs from remaining visible: in a document-flow application,
190
+ a persistent header may scroll off screen. Section 4 explains the sizing
191
+ choice. The example's `active-only` policy is a deliberate choice, not the
192
+ default; section 3 explains alternatives.
193
+
194
+ ### Apply the same pattern within a screen
195
+
196
+ A record workspace can keep its navigator and selected record while changing
197
+ only the summary/history tab. In this excerpt, `selection` is an
198
+ application-owned writable record-key emitter shared by the three application
199
+ components; its owner outlives both tab contents.
200
+
201
+ ```tsx
202
+ <Layout horizontal allocation="flexible" ariaLabel="Record workspace">
203
+ <Sidebar allocation="natural" className="record-navigation"
204
+ island header="Records">
205
+ <RecordNavigator selection={selection} />
206
+ </Sidebar>
207
+ <Panel allocation="flexible" island header="Selected record">
208
+ <TabPanel label="Record sections" className="cap-size-flexible"
209
+ mountPolicy="lazy">
210
+ <Tab id="summary" label="Summary">
211
+ <RecordSummary selection={selection} />
212
+ </Tab>
213
+ <Tab id="history" label="History">
214
+ <RecordHistory selection={selection} />
215
+ </Tab>
216
+ </TabPanel>
217
+ </Panel>
218
+ </Layout>
219
+ ```
220
+
221
+ Application CSS sets the navigator's width. `Sidebar` owns scrolling for its
222
+ contents; `TabPanel` provides scrolling tabpanel containers. The two islands
223
+ are siblings. The tab panel does not introduce another island inside the
224
+ selected-record surface.
225
+
226
+ This local arrangement persists while switching its tabs. Its lifetime when
227
+ leaving the whole screen is a separate top-level outlet decision. Add route
228
+ descriptors to the tabs when those destinations should be addressable through
229
+ the router; local tab selection alone does not require URLs.
230
+
231
+ ### Share definitions and instances deliberately
232
+
233
+ Two pages can each use the same layout component and receive separate mounted
234
+ instances. This centralizes structure without making their controls, selection,
235
+ or scroll positions global. If the same region should survive navigation, move
236
+ its owning instance above the relevant outlet instead.
237
+
238
+ A shared sidebar belongs in the shell when its purpose and desired lifetime
239
+ are application-wide. Sidebars with different page-specific controls can stay
240
+ inside their pages, even when they share width, styling, and arrangement.
241
+
242
+ When a page changes content in shared chrome, first determine whether that
243
+ content needs to live there. Page-specific actions can often remain in the
244
+ page's toolbar. For a genuinely shared header or status region, let the shell
245
+ compose content from the active selection and application-owned values or
246
+ callbacks. A supplied outlet `valueEmitter` can also be observed by other
247
+ regions; only the outlet registers the routes. Keep presentation markup in
248
+ components and data/operations in services. Avoid having mounted pages locate
249
+ and modify shell DOM or leave global toolbar registrations behind on exit.
250
+
251
+ ### Arrange form fields in vertical groups
252
+
253
+ Controls in a form read top to bottom. Choose the grouping component by the
254
+ relationship among its contents: `OptionGroup` is a fieldset of *peer*
255
+ controls answering one narrow concern — its legend names the question —
256
+ while `GroupBox` gathers *distinct* fields under one broader subject, its
257
+ header naming the topic. Stack each group's controls in a vertical `Layout`.
258
+ When a form has several groups, place them side by side in a horizontal
259
+ `Layout` so the columns use the available width and wrap when it runs out;
260
+ each group still owns its vertical field order.
261
+
262
+ ```tsx
263
+ <Panel header="Connection" context="form">
264
+ <Layout horizontal className="form-groups">
265
+ <GroupBox header="Server">
266
+ <Layout vertical>
267
+ <Textbox label="Host" valueEmitter={state.host} />
268
+ <Textbox label="Port" valueEmitter={state.port} />
269
+ </Layout>
270
+ </GroupBox>
271
+ <GroupBox header="Credentials">
272
+ <Layout vertical>
273
+ <Textbox label="User" valueEmitter={state.user} />
274
+ <Textbox label="Password" type="password"
275
+ valueEmitter={state.password} />
276
+ </Layout>
277
+ </GroupBox>
278
+ <OptionGroup label="Protocol">
279
+ <Layout vertical>
280
+ <Checkbox label="TLS" valueEmitter={state.tls} />
281
+ <Checkbox label="Compression"
282
+ valueEmitter={state.compression} />
283
+ </Layout>
284
+ </OptionGroup>
285
+ </Layout>
286
+ </Panel>
287
+ ```
288
+
289
+ ```css
290
+ .form-groups { flex-wrap: wrap; align-items: flex-start; gap: 1rem 2rem; }
291
+ ```
292
+
293
+ `Panel` and `Layout` accept `context="control" | "form"` to declare a
294
+ presentation context for their descendants; the nearest marked ancestor
295
+ wins. In a `form` context `GroupBox` keeps its border but drops the
296
+ sidebar-weighted header chrome — the section header becomes a plain
297
+ text-colored label above the content — while the unmarked default keeps
298
+ the control presentation.
299
+ `OptionsBox` is the `GroupBox` specialization for sidebar panels of
300
+ `OptionGroup`s. Prefer these components over anonymous wrappers: the
301
+ fieldset, legend, and vertical rhythm are the contract a theme styles.
302
+
303
+ ## 3. Choose state lifetime and transition boundaries
304
+
305
+ Decide what the user should find when returning to a screen. A draft may need
306
+ to survive navigation; a hover detail may not. Preserving a filter value does
307
+ not necessarily require preserving the entire results DOM.
308
+
309
+ `RouteOutlet` and `TabPanel` offer the same `mountPolicy` choices:
310
+
311
+ | Policy | Content lifetime | Use when |
312
+ | --- | --- | --- |
313
+ | `eager` (default) | Mount every branch and retain it | All branches should initialize immediately |
314
+ | `lazy` | Mount on first selection, then retain visited branches | Reusing visited component/DOM state matters |
315
+ | `active-only` | Destroy inactive content and mount the selected branch | Recreating views is appropriate and inactive content should be released |
316
+
317
+ Retained content remains mounted and can keep subscriptions and queries active.
318
+ It is not automatically paused while hidden. Destroying a view cleans up its
319
+ rendered subtree and renderer-managed subscriptions and runs its cleanup hooks.
320
+ Arrange disposal of application-created queries, emitters, and subscriptions
321
+ through their owner's `onCleanup()` or `onDestroy()`; simply storing an object
322
+ in a component field does not arrange its disposal. A destroyed view does not
323
+ dispose a shared application service or stop work owned elsewhere. Choose query
324
+ activation and disposal with the same care as component lifetime. Capillary UI does not
325
+ fetch merely because a route exists.
326
+
327
+ Keep state that must survive a destroyed view in an owner that outlives it,
328
+ such as its enclosing workspace or an application-owned service. Keep local
329
+ state local when its lifetime should match the view. Create emitters, queries,
330
+ and other owned objects at their lifetime boundary, rather than creating new
331
+ ones on every render. Layout convenience is not a reason to mirror all values
332
+ into a global UI store.
333
+
334
+ Use recreatable TSX/VNodes for `active-only` branches. A destroyed component
335
+ instance cannot be mounted again. Use stable keys to preserve sibling identity;
336
+ changing a key intentionally resets that subtree. Neither retained DOM nor
337
+ long-lived state implies persistence across a browser reload.
338
+
339
+ ### Confine loading and errors to the affected work
340
+
341
+ If only results are loading, keep the shell, filters, and useful actions
342
+ available. A results component can observe a query snapshot and choose loading,
343
+ error, empty, or populated output inside its assigned region. Returning a
344
+ spinner before rendering the entire page frame removes that frame and its
345
+ local component state too.
346
+
347
+ Decide whether a refresh keeps the previous result visible with a busy indicator
348
+ or clears it. Disable actions whose prerequisites are unavailable rather than
349
+ disabling unrelated navigation. An empty result still occupies a meaningful
350
+ results region in a fullscreen workspace. Application-wide startup failures
351
+ may justify a broader boundary; the scope should match what is unavailable.
352
+
353
+ Details opened by a selection follow the same principle: the page owns the
354
+ selection and close action, while a details component presents the selected
355
+ information. A page can conditionally include details without making every
356
+ shared layout aware of selection policy.
357
+
358
+ See [component lifecycle](../README.md#components-and-lifecycle),
359
+ [reactive templates](../README.md#reactive-templates),
360
+ [application services](../README.md#application-services), and
361
+ [routing](../README.md#browser-routing) for implementation contracts.
362
+
363
+ ## 4. Choose viewport allocation or document flow
364
+
365
+ Make this choice from the intended interaction, before adding scrollbars.
366
+
367
+ | Model | Extent and scrolling | Typical uses |
368
+ | --- | --- | --- |
369
+ | Viewport allocation | A bounded root allocates remaining space; designated inner regions scroll | Workspaces, editors, monitoring screens |
370
+ | Document flow | Content determines height; the browser document scrolls | Articles, long registers, record pages, portals |
371
+
372
+ A horizontal arrangement, a grid, or a set of islands does not decide which
373
+ model applies. A dashboard can use either model.
374
+
375
+ ### Keep the bounded chain intact
376
+
377
+ For a viewport application, `CapillaryUiApp.sizing="viewport"` supplies the external
378
+ bound. The bound only holds when the document cooperates: remove the browser's
379
+ default `body` margin (`html, body { margin: 0 }`), which would otherwise push
380
+ the `100vh` root into document scrollbars. `layout="vertical"` arranges its
381
+ direct children. Each intermediate DOM
382
+ container must carry the allocation to the region that needs it:
383
+
384
+ ```text
385
+ viewport root, vertical arrangement
386
+ ├── header and navigation: natural
387
+ ├── main: flexible, vertical arrangement
388
+ │ └── outlet and active page: carry the available space
389
+ │ ├── toolbar: natural
390
+ │ └── results: flexible, scroll owner
391
+ └── footer: natural
392
+ ```
393
+
394
+ Capillary UI's public traits express the common mechanics when a semantic native
395
+ element is the appropriate layout boundary:
396
+
397
+ - `cap-layout-horizontal` / `cap-layout-vertical`: arrange direct children.
398
+ - `cap-size-natural`: retain the content/application allocation on the parent's
399
+ main axis; this does not itself set a fixed width or height.
400
+ - `cap-size-flexible`: share remaining space and permit shrinking below
401
+ intrinsic content size through zero logical minimums.
402
+ - `cap-scroll`: make an already bounded region an overflow owner.
403
+
404
+ Flexible sizing does not imply scrolling. An auto-height wrapper does not
405
+ inherit a viewport bound just because a distant ancestor has one. Extracting a
406
+ component that adds a DOM wrapper can therefore change layout: preserve the
407
+ DOM shape or deliberately carry allocation through the new host.
408
+
409
+ Here is one possible `RecordsView` for the shell above. `RecordFilters` and
410
+ `RecordsResults` resolve or receive the application's shared view state; the
411
+ former renders controls and the latter renders the result/loading/error
412
+ content. The frame remains present through those states.
413
+
414
+ ```tsx
415
+ class RecordsView extends Component {
416
+ render() {
417
+ return <Layout horizontal allocation="flexible"
418
+ className="records-workspace" ariaLabel="Find records">
419
+ <aside className="record-filters island cap-size-natural cap-scroll"
420
+ aria-label="Record filters" tabIndex={0}>
421
+ <RecordFilters />
422
+ </aside>
423
+ <Panel allocation="flexible" header="Matching records" scroll={false}>
424
+ <PanelToolbar>
425
+ <Toolbar allocation="natural" label="Result actions">
426
+ <button type="button" onClick={exportRecords}>Export</button>
427
+ </Toolbar>
428
+ </PanelToolbar>
429
+ <Layout vertical allocation="flexible" scroll
430
+ ariaLabel="Record results" tabIndex={0}>
431
+ <RecordsResults />
432
+ </Layout>
433
+ </Panel>
434
+ </Layout>
435
+ }
436
+
437
+ static dependencies = [Layout, Panel, PanelToolbar, RecordFilters, RecordsResults, Toolbar]
438
+ }
439
+ ```
440
+
441
+ `exportRecords` is an application action. Application CSS supplies dimensions
442
+ and spacing, for example:
443
+
444
+ ```css
445
+ .records-workspace { gap: 1rem; }
446
+ .record-filters { inline-size: 18rem; }
447
+ .record-navigation { inline-size: 18rem; }
448
+ ```
449
+
450
+ The filters and results have separate scroll owners, and the toolbar stays
451
+ outside the results scrollbar. The application must adapt the widths or
452
+ arrangement when the available space cannot accommodate both regions. Generic
453
+ scroll regions need appropriate accessible names and keyboard access; consider
454
+ the focusability already provided by their contents when choosing tab stops.
455
+
456
+ Use component arguments where they target the intended element. `Layout` and
457
+ `Panel` use `horizontal` or `vertical` for their arranged content, while
458
+ supported components use `allocation` for their outer host. `Panel` applies
459
+ its direction to its inner Layout body rather than its generated header or
460
+ toolbar. A generic layout trait on another component host might instead
461
+ arrange generated chrome. Support is explicit; do not assume every component
462
+ accepts the same layout arguments.
463
+
464
+ Components such as `Sidebar`, `Panel`, `TabPanel`, and `RouteOutlet` already
465
+ have layout/overflow behavior. Inspect that contract before adding another
466
+ scroll container. Existing ancestor overflow can be an inactive fallback;
467
+ verify which element actually has scroll range. Avoid concealing allocation
468
+ errors with blanket clipping.
469
+
470
+ ### Let a document grow
471
+
472
+ For a traditional register, a content-oriented `RecordsResults` can contribute
473
+ its full height to the page. This is an alternative root, not a child placed
474
+ inside the preceding viewport shell:
475
+
476
+ ```tsx
477
+ class RegisterApp extends CapillaryUiApp {
478
+ protected override renderContent() {
479
+ return <main className="register-page">
480
+ <h1>Record register</h1>
481
+ <RecordFilters />
482
+ <RecordsResults />
483
+ </main>
484
+ }
485
+
486
+ static dependencies = [RecordFilters, RecordsResults]
487
+ }
488
+
489
+ mountCapillaryUiApp(createCapillaryUiRuntime(), RegisterApp, document.querySelector('#app')!, {
490
+ sizing: 'embedded',
491
+ landmark: 'none',
492
+ })
493
+ ```
494
+
495
+ ```css
496
+ .register-page {
497
+ max-inline-size: 70rem;
498
+ margin-inline: auto;
499
+ padding: 1rem;
500
+ }
501
+ ```
502
+
503
+ There is no bounded results container here. More rows increase page height.
504
+ The surrounding host page must also allow document flow. Setting an inner
505
+ component to `embedded` inside an already constrained scrolling shell does not
506
+ transfer scrolling to the browser document.
507
+
508
+ Keep reusable result content free of a forced viewport height when callers
509
+ need both uses. A deliberately bounded result widget can instead document that
510
+ requirement and be used only where appropriate. Large data sets still need
511
+ application-owned query limits, pagination, or another suitable strategy;
512
+ document scrolling does not remove the cost of rendering rows.
513
+
514
+ ### Adapt the composition without changing the ownership rules
515
+
516
+ - A classic fullscreen shell reserves natural space for chrome and allocates
517
+ the rest to its workspace.
518
+ - A data workspace separates control allocation from a flexible results region.
519
+ - A workbench repeats the bounded chain through nested panes. Make size ratios
520
+ and each pane's scroll owner intentional. `SplitView` supplies a two-pane
521
+ composition and accessible resizing mechanics, but not size persistence or
522
+ responsive policy.
523
+ - A fullscreen monitoring screen can allocate equal shares to similarly
524
+ decorated sibling regions with independent scrolling. Equal flexible growth
525
+ does not guarantee equal outer boxes with different padding or borders.
526
+ - Articles and registers grow in document flow; record/detail and portal pages
527
+ can add application-owned columns or grids while retaining that behavior.
528
+
529
+ Application CSS owns precise widths, ratios, gaps, maximum sizes, and responsive
530
+ rearrangement. Capillary UI does not provide breakpoint variants. Prefer responsive CSS
531
+ when the same component tree can serve the smaller layout, and keep visual,
532
+ reading, and keyboard order coherent. If a changed structure remounts content,
533
+ account for its state and focus lifetime explicitly.
534
+
535
+ See [root sizing](../README.md#root-sizing-and-typography) and
536
+ [layout traits](../README.md#reusable-traits) for the public contract.
537
+
538
+ ## 5. Extract components that establish a useful contract
539
+
540
+ A reader should see the screen's major regions, their contents, and the values
541
+ connecting them. The internals of a known component can stay behind its API.
542
+ Readable composition does not require placing every control in one large
543
+ render method.
544
+
545
+ Use this decision table when similar markup appears:
546
+
547
+ | What is actually shared? | A useful starting point |
548
+ | --- | --- |
549
+ | Styling, spacing, or widths, with varying anatomy | Native markup and shared CSS traits/tokens |
550
+ | A fixed arrangement with a few meaningful content regions | A shared layout component accepting parent-specific named region children |
551
+ | An accessible interaction or recognizable widget | A component such as `GroupBox`, `Sidebar`, or a domain-specific presentation |
552
+ | A substantial part of one screen | A component colocated with that screen, even if it has only one caller |
553
+ | Mostly another component's props, passed straight through | Keep the direct use unless the wrapper adds a meaningful contract |
554
+
555
+ There is no fixed number of repeated lines or callers that makes extraction
556
+ correct. Ask whether these structures should change together and whether the
557
+ new API lets readers trust what is hidden. Two visually similar sections may
558
+ need to evolve independently.
559
+
560
+ For example, a `FilterSidebar` that only renders supplied children followed by
561
+ a status-filter panel hides their ordering without owning a sidebar or useful
562
+ behavior. Declaring those siblings in the view can be clearer. A `RecordFilters`
563
+ component that owns a recognizable set of application filters and their reset
564
+ interaction provides a stronger contract, even if its implementation is small.
565
+
566
+ ### Pass content as content
567
+
568
+ Use props to configure a component. Use ordinary children for ordered content
569
+ in one region. Use parent-specific named region children when a template has
570
+ several distinct content roles.
571
+
572
+ Configuration props include identifiers, short labels, state bindings,
573
+ callbacks, allocation modes, accessibility names, and other values that remain
574
+ easy to read on the component's opening tag. A compact heading such as
575
+ `header="Display options"` is also reasonable there. Props should rarely carry
576
+ a substantial `CapillaryUiChild` tree: important structure becomes punctuation-heavy
577
+ and disappears from the visible parent/child hierarchy.
578
+
579
+ Choose the content API from what the parent does with it:
580
+
581
+ | Content relationship | Preferred API |
582
+ | --- | --- |
583
+ | One body whose children render in authored sequence | Ordinary children |
584
+ | A homogeneous ordered collection | Ordered declarative item children such as `Tab` |
585
+ | Several regions with different roles | Parent-specific declarative region children |
586
+ | Elements genuinely generated from metadata | A typed data/model prop |
587
+
588
+ If a component simply renders several supplied elements in one panel body,
589
+ ordinary children are already the ordered contract. Do not assign special
590
+ meaning to child indexes unnecessarily:
591
+
592
+ `GroupBox` owns its labeled group structure and presentation. Its caller owns
593
+ the controls. For example, `state.colorBy` and `state.relativeTo` below are
594
+ writable emitters created by the view's state owner:
595
+
596
+ ```tsx
597
+ <GroupBox header="Display options">
598
+ <RadioGroup
599
+ label="Colors represent"
600
+ options={[
601
+ ['status', 'Status'],
602
+ ['owner', 'Owner'],
603
+ ]}
604
+ valueEmitter={state.colorBy}
605
+ />
606
+ <RadioGroup
607
+ label="Compare against"
608
+ options={[
609
+ ['selection', 'Selection'],
610
+ ['all', 'All records'],
611
+ ]}
612
+ valueEmitter={state.relativeTo}
613
+ />
614
+ </GroupBox>
615
+ ```
616
+
617
+ This keeps the controls, their order, and their bindings visible while sharing
618
+ the group chrome. `OptionsBox` provides a more specific arrangement for
619
+ option groups when that is the intended structure.
620
+
621
+ A wrapper that accepts an array of radio-group specifications merely to
622
+ reconstruct these elements adds another authoring format and must forward each
623
+ control capability. Prefer the existing composition when the structure is
624
+ authored directly. Data-driven definitions are appropriate when the controls
625
+ really come from metadata or when the component owns a meaningful model.
626
+
627
+ When regions have different meanings, name them with parent-specific
628
+ components. SplitView's named regions are specialized Layout panes rather than
629
+ non-visual markers:
630
+
631
+ ```tsx
632
+ <SplitView horizontal allocation="flexible" primarySize="18rem"
633
+ separatorLabel="Resize record navigation">
634
+ <SplitPrimary vertical scroll label="Record navigation">
635
+ <RecordNavigator selection={selection} />
636
+ </SplitPrimary>
637
+ <SplitSecondary vertical scroll label="Record details">
638
+ <RecordSummary selection={selection} />
639
+ <RecordHistory selection={selection} />
640
+ </SplitSecondary>
641
+ </SplitView>
642
+ ```
643
+
644
+ SplitView owns the separator's pointer and keyboard behavior and reports sizes;
645
+ the application owns persistence and responsive policy. `PanelToolbar`,
646
+ `SidebarToolbar`, `DialogActions`, and
647
+ `OptionGroupHeaderEnd` provide the corresponding named insertion points for
648
+ those components. Their contents remain nested in the call site instead of
649
+ being hidden in `toolbar={...}` or `actions={...}` props. Short heading and
650
+ label props remain configuration:
651
+
652
+ ```tsx
653
+ <Panel header="Matching records">
654
+ <PanelToolbar>
655
+ <Toolbar label="Result actions">
656
+ <Button label="Export" onClick={exportRecords} />
657
+ </Toolbar>
658
+ </PanelToolbar>
659
+ <RecordsResults />
660
+ </Panel>
661
+ ```
662
+
663
+ Use positional region assignment only when every position receives the same
664
+ treatment and ordering is the complete meaning—for example, an equal-panel
665
+ component that wraps each ordinary child in the same panel. If “first” means
666
+ navigation and “second” means workspace, explicit names are more resilient and
667
+ readable.
668
+
669
+ Prefer semantic marker names such as `ShellHeader` and `ShellContent` over a
670
+ universal `<Slot name="header">`. The supported anatomy is then visible in the
671
+ import and TSX types; two parents cannot silently give the same string name
672
+ different contracts. The shared parsing mechanism may be generic, but the
673
+ public composition language should describe the region's role.
674
+
675
+ ### Define a component with named regions
676
+
677
+ Application-defined templates can extend `DeclarativeRegion` for each role and
678
+ use `readDeclarativeRegions()` to consume their direct children:
679
+
680
+ ```tsx
681
+ class ShellHeader extends DeclarativeRegion {}
682
+ class ShellContent extends DeclarativeRegion {}
683
+ class ShellFooter extends DeclarativeRegion {}
684
+
685
+ class ApplicationShell extends Component {
686
+ render() {
687
+ const {regions} = readDeclarativeRegions(
688
+ 'ApplicationShell',
689
+ this.props.children,
690
+ {
691
+ header: ShellHeader,
692
+ content: ShellContent,
693
+ footer: ShellFooter,
694
+ },
695
+ {allowContent: false, required: ['content']},
696
+ )
697
+
698
+ return <Layout vertical className="application-shell">
699
+ {regions.header == null ? null : <header>{regions.header}</header>}
700
+ <main className="cap-size-flexible">{regions.content}</main>
701
+ {regions.footer == null ? null : <footer>{regions.footer}</footer>}
702
+ </Layout>
703
+ }
704
+
705
+ static dependencies = [Layout, ShellHeader, ShellContent, ShellFooter]
706
+ }
707
+ ```
708
+
709
+ The resulting use keeps the supplied anatomy visible:
710
+
711
+ ```tsx
712
+ <ApplicationShell>
713
+ <ShellHeader>
714
+ <Brand />
715
+ <NavigationBar label="Application sections" items={navigationItems} />
716
+ </ShellHeader>
717
+ <ShellContent>
718
+ <RouteOutlet views={routes} />
719
+ </ShellContent>
720
+ <ShellFooter>
721
+ <ConnectionStatus />
722
+ </ShellFooter>
723
+ </ApplicationShell>
724
+ ```
725
+
726
+ Region markers are non-visual instructions, not additional surfaces or DOM
727
+ wrappers. They must be direct children of the parent that documents them.
728
+ `readDeclarativeRegions()` preserves ordinary content order, rejects duplicate
729
+ or foreign region markers, can reject ordinary content, and can require named
730
+ regions. Keep the region set small and stable. Put reactive values inside a
731
+ region rather than making the template anatomy itself a changing stream.
732
+
733
+ A named region exposes where caller-owned content belongs; it does not reveal
734
+ or transfer the parent's other responsibilities. The parent still owns the
735
+ rendered landmarks, allocation, scrolling, accessibility wiring, and region
736
+ order. Source order should normally match rendered and keyboard order.
737
+
738
+ Expose only genuine variability. If every caller receives the same application
739
+ header or footer, render it inside the shell rather than adding a region merely
740
+ because the structure has a name. A single-use shell can remain direct markup
741
+ in the application root; named regions do not make an otherwise unnecessary
742
+ abstraction valuable.
743
+
744
+ Prefer a few meaningful regions over a universal panel whose many options
745
+ change its topology. If callers repeatedly need to inspect internals or target
746
+ private descendants to place content correctly, reconsider the boundary.
747
+
748
+ ### Make a shared layout's promises explicit
749
+
750
+ A small layout component can earn its place by preserving a reliable sizing
751
+ chain and scroll boundary. State its contract in ordinary terms:
752
+
753
+ > This workspace fills its parent's allocated space, keeps a toolbar above
754
+ > the results, and gives the results region the scrollbar.
755
+
756
+ Document what space it expects, how its host participates, where content is
757
+ placed, and whether it owns scrolling or delegates it. If it deliberately
758
+ supports both document flow and bounded allocation, make those modes explicit
759
+ and verify both. It need not expose every CSS property as a prop.
760
+
761
+ Sharing CSS centralizes presentation but leaves structural markup repeated.
762
+ Extracting a layout centralizes structural changes but asks readers to learn
763
+ its contract. Choose based on the changes that should remain coordinated.
764
+ Keep arrangement and scrolling visible in the TSX of the component that owns
765
+ them; callers can then rely on its documented contract.
766
+
767
+ ### Prefer composition for screens; use inheritance deliberately
768
+
769
+ A view can contain a layout and supply its contents. Making every view inherit
770
+ from a layout spreads the declaration across overridden methods and does not
771
+ make the layout instance persist across navigation. Composition is the usual
772
+ choice for assembling application screens.
773
+
774
+ Inheritance remains useful where Capillary UI provides an intentional specialization
775
+ contract. An application root can extend `CapillaryUiApp` and override
776
+ `renderContent()`, as above. `OptionsBox` extends `GroupBox` to specialize
777
+ shared chrome. Such examples do not require an application-wide hierarchy of
778
+ page base classes.
779
+
780
+ ## 6. Share styling without multiplying surfaces
781
+
782
+ Use an island for a meaningful work surface: a navigation area, a results
783
+ workspace, or a substantial analysis region. Do not turn every extracted
784
+ component into a separate island. Several controls, groups, and data views can
785
+ belong to one island; islands must not nest.
786
+
787
+ Let the composition that knows the surrounding surfaces choose island
788
+ placement. A reusable inner component should normally leave that choice to
789
+ its caller. The `island` prop or class supplies surface treatment, not space
790
+ allocation or the intended scroll owner. A `GroupBox` inside an island can
791
+ retain its ordinary group chrome without itself being another island.
792
+
793
+ Use native elements for native semantics. Apply Capillary UI's public traits directly
794
+ to those elements when they participate in Capillary UI layout or presentation. For
795
+ example, an application shell can naturally use
796
+ `<header className="island cap-size-natural">` and
797
+ `<footer className="island cap-size-natural">`; a Capillary UI-specific header or
798
+ footer component is not required merely to obtain the standard surface
799
+ treatment.
800
+
801
+ Conversely, Capillary UI does not infer that treatment from the native element type
802
+ alone. A plain `header`, `footer`, `section`, or `aside` remains ordinary
803
+ application markup until the application explicitly opts it into a Capillary UI trait or
804
+ places it inside a Capillary UI-owned component contract. Share application CSS through
805
+ meaningful traits rather than extracting components solely to attach a class.
806
+ Components own their structural presentation; themes and palette assets supply
807
+ the chosen visual treatment. Follow the
808
+ [styling contract](../README.md#styling-contract) rather than duplicating
809
+ internal component styles in every screen.
810
+
811
+ Development controls are another application-owned boundary. If an app provides
812
+ forced loading, disabled, or validation states for testing, apply them to the
813
+ intended content while leaving the controls that restore normal operation
814
+ usable. A demo harness is not a required layer of every Capillary UI application.
815
+
816
+ ## 7. Review the design through real transitions
817
+
818
+ Before treating a composition or shared layout as established, check:
819
+
820
+ - Can a reader identify the screen's task, major regions, and control/result
821
+ relationships without opening a chain of forwarding wrappers?
822
+ - Does navigation replace only the intended content? Test return navigation and
823
+ direct nested URLs when routing is enabled.
824
+ - Do drafts, selections, and filters survive or reset deliberately? Are owned
825
+ subscriptions and queries released at the right lifetime boundary?
826
+ - During loading, refresh, error, and empty results, do useful controls remain
827
+ available and is feedback confined to the affected region?
828
+ - With overflowing content, which elements actually scroll? In a viewport
829
+ layout, verify that content does not accidentally grow the document or
830
+ create competing ancestor scrollbars.
831
+ - With little or no content, does a bounded workspace still fill its allocation
832
+ and keep bottom chrome in place? In a document layout, does more content grow
833
+ the document naturally?
834
+ - At narrow widths and enlarged text, do controls remain reachable? Check
835
+ landmarks, headings, region names, keyboard order, and focus after content
836
+ changes. Do not rely on clipping to make geometry appear correct.
837
+ - Are shared structure and styling centralized where they should change
838
+ together, while page-specific decisions remain easy to find?
839
+
840
+ For reusable layouts, browser geometry checks can verify bounds and actual
841
+ scroll ranges; visual and keyboard review checks how the composition feels to
842
+ use. A screenshot of one populated desktop state does not establish the whole
843
+ contract.