@skra/ui 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,90 +1,92 @@
1
- # Consuming Skra — Agent Guide (Core)
2
-
3
- > **DRAFT (2026-06-25; 8th section added 2026-08-30; 9th — Feedback — 2026-09-27).** All 9 sections drafted. Snippets are grounded in source (verified
4
- > against `libs/ui/*`) + the `apps/dogfood` friction journal (`F-n`) + the Iron Boundaries.
5
- > Still draft: not yet referenced from a real consumer repo, not lint-gated against ADR drift,
6
- > and cross-engine/Safari caveats inherit Skra's own open item O-9.
7
-
8
- This guide is for an **agent working inside a repository that consumes Skra** (imports
9
- `@skra/*`), not for authoring Skra itself. It is written to be **copied into / referenced
10
- from a consumer repo**.
11
-
12
- ---
13
-
14
- ## How to use this guide (read this first — it is the point)
15
-
16
- This file is **tiered on purpose** (same model as `FORGE_ADR_SUMMARY.md`). To keep token
17
- cost low:
18
-
19
- 1. **Always load this core** (it is small) — the Iron Rules + the Section Router below.
20
- 2. Then load **at most the ONE section** that matches your current task (the Router maps
21
- task → file). Do **not** read every section "to be safe" — that defeats the design.
22
- 3. Open the full Skra ADR / spec only when a section explicitly points you there.
23
-
24
- ---
25
-
26
- ## Iron Rules of Consumption (the 6 a consumer breaks on day one)
27
-
28
- Violating these is a review blocker in a consumer repo just as inside Skra.
29
-
30
- 1. **Import only from public `@skra/ui/*` entry points** — never a deep relative `libs/...`
31
- path. Skra ships as one package, `@skra/ui`, with a secondary entry point per component
32
- (e.g. `@skra/ui/button`, `@skra/ui/typography`, `@skra/ui/select`); the primary
33
- `@skra/ui` exports nothing. (Full entry-point list → `setup-and-gotchas.md`.)
34
- 2. **You own the value.** Skra form atoms do **not** implement `ControlValueAccessor` /
35
- `FormValueControl` (ADR 037). Native-host atoms take `[formField]`/`[formControl]`/
36
- `[(ngModel)]`; value-owned overlays need an explicit `[value]`/`(valueChange)` bridge.
37
- (How → `forms-and-binding.md`.)
38
- 3. **Accessible name has exactly ONE path** (ADR 033). No Skra atom exposes an `ariaLabel`
39
- input; never put `aria-label` next to visible text on the same host. (Paths →
40
- `accessible-names.md`.)
41
- 4. **Inside `<skra-field>`, do not pass `id` / `for` / `aria-describedby` by hand** — the
42
- DI context wires label↔control↔hint↔error automatically (ADR 013).
43
- 5. **Under Signal Forms, native constraint attributes go in the schema, not the template.**
44
- `name` on grouped radios and `maxlength` / `min` / `pattern` on a `[formField]` host raise
45
- `NG8022` — Signal Forms owns them (F-1/F-2). (Details → `forms-and-binding.md`.)
46
- 6. **Never theme with inline styles.** No `[style.--skra-*]` bindings or `style="…"` (breaks
47
- CSP, ADR 021). Override tokens via a CSS class / `data-skra-*` scope; components are
48
- `margin: 0` (ADR 022) — spacing comes from layout atoms, never a margin on the component.
49
-
50
- ---
51
-
52
- ## Section Router — load the ONE that matches your task
53
-
54
- | Section | File | Load when | Status |
55
- | --------------------------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------- | -------- |
56
- | **Setup & gotchas** | `setup-and-gotchas.md` | First integration; import fails; which `@skra/*` package exports what; boolean-attribute & theming pitfalls | ✅ draft |
57
- | **Forms & value binding** | `forms-and-binding.md` | Wiring an input / select / checkbox / rating to Signal Forms, Reactive Forms, or `ngModel` | ✅ draft |
58
- | **Accessible names** | `accessible-names.md` | Naming buttons, icon-only controls, overlays; using `skra-sr-only` | ✅ draft |
59
- | **Theming & encapsulation** | `theming-and-encapsulation.md` | Skins, dark mode, density; mounting under Shadow DOM / a microfrontend / an iframe | ✅ draft |
60
- | **Feedback** | `feedback.md` | Alert vs toast vs badge vs field error; `live` announcing; dismissal; an alert with a next-step action | ✅ draft |
61
- | **Overlays** | `overlays.md` | Dialog / tooltip / popover / menu / select in the top layer; shadow & iframe limits | ✅ draft |
62
- | **Layout** | `layout.md` | Spacing & composition with Stack / Box / Surface / Grid; building a card; the gap scale | ✅ draft |
63
- | **Wrapping Skra** | `wrapping-patterns.md` | Building your own higher-level components over Skra atoms (simplified API) | ✅ draft |
64
- | **Custom components** | `custom-components.md` | Authoring a net-new widget Skra does not ship (nothing to wrap) on Skra tokens — own token tier, state, promotion path | ✅ draft |
65
-
66
- ---
67
-
68
- ## Quick reference — task → section
69
-
70
- | I want to… | Go to |
71
- | ----------------------------------------------------------------------- | ----------------------------------------- |
72
- | Add `@skra/ui/*` to a project and import an atom | `setup-and-gotchas.md` |
73
- | Bind a text input / textarea to a form | `forms-and-binding.md` |
74
- | Bind a `skra-select` / `skra-datepicker` / `skra-star-rating` to a form | `forms-and-binding.md` (the value bridge) |
75
- | Name an icon-only button | `accessible-names.md` |
76
- | Switch skin / dark mode at runtime | `theming-and-encapsulation.md` |
77
- | Mount Skra inside a Web Component / microfrontend | `theming-and-encapsulation.md` |
78
- | Show an alert / toast / badge, or add a "Try again" button to an alert | `feedback.md` |
79
- | Open a dialog or tooltip | `overlays.md` |
80
- | Space components apart | `layout.md` |
81
- | Build a card / panel / tile (there is no `skra-card`) | `layout.md` (Surface) |
82
- | Wrap a Skra atom in a project-specific component | `wrapping-patterns.md` |
83
- | Build a widget Skra does not ship (scrubber, timeline, domain visual) | `custom-components.md` |
84
- | Keep an app-local component on-skin, on-density, dark-mode-correct | `custom-components.md` |
85
-
86
- ---
87
-
88
- _Source of truth for the rules: Skra `FORGE_ADR_SUMMARY.md` + `adr/decisions/`. Real
89
- consumption evidence: `apps/dogfood/FRICTION_JOURNAL.md`. This guide restates the consumer
90
- slice of those — on divergence, the ADRs win._
1
+ # Consuming Skra — Agent Guide (Core)
2
+
3
+ > **DRAFT (2026-06-25; 8th section added 2026-08-30; 9th — Feedback — 2026-09-27).** All 9 sections drafted. Snippets are grounded in source (verified
4
+ > against `libs/ui/*`) + the `apps/dogfood` friction journal (`F-n`) + the Iron Boundaries.
5
+ > Still draft: not yet referenced from a real consumer repo, not lint-gated against ADR drift,
6
+ > and cross-engine/Safari caveats inherit Skra's own open item O-9.
7
+
8
+ This guide is for an **agent working inside a repository that consumes Skra** (imports
9
+ `@skra/*`), not for authoring Skra itself. It is written to be **copied into / referenced
10
+ from a consumer repo**.
11
+
12
+ ---
13
+
14
+ ## How to use this guide (read this first — it is the point)
15
+
16
+ This file is **tiered on purpose** (same model as `FORGE_ADR_SUMMARY.md`). To keep token
17
+ cost low:
18
+
19
+ 1. **Always load this core** (it is small) — the Iron Rules + the Section Router below.
20
+ 2. Then load **at most the ONE section** that matches your current task (the Router maps
21
+ task → file). Do **not** read every section "to be safe" — that defeats the design.
22
+ 3. Open the full Skra ADR / spec only when a section explicitly points you there.
23
+
24
+ ---
25
+
26
+ ## Iron Rules of Consumption (the 6 a consumer breaks on day one)
27
+
28
+ Violating these is a review blocker in a consumer repo just as inside Skra.
29
+
30
+ 1. **Import only from public `@skra/ui/*` entry points** — never a deep relative `libs/...`
31
+ path. Skra ships as one package, `@skra/ui`, with a secondary entry point per component
32
+ (e.g. `@skra/ui/button`, `@skra/ui/typography`, `@skra/ui/select`); the primary
33
+ `@skra/ui` exports nothing. (Full entry-point list → `setup-and-gotchas.md`.)
34
+ 2. **You own the value.** Skra form atoms do **not** implement `ControlValueAccessor` /
35
+ `FormValueControl` (ADR 037). Native-host atoms take `[formField]`/`[formControl]`/
36
+ `[(ngModel)]`; value-owned overlays need an explicit `[value]`/`(valueChange)` bridge.
37
+ (How → `forms-and-binding.md`.)
38
+ 3. **Accessible name has exactly ONE path** (ADR 033). No Skra atom exposes an `ariaLabel`
39
+ input; never put `aria-label` next to visible text on the same host. (Paths →
40
+ `accessible-names.md`.)
41
+ 4. **Inside `<skra-field>`, do not pass `id` / `for` / `aria-describedby` by hand** — the
42
+ DI context wires label↔control↔hint↔error automatically (ADR 013).
43
+ 5. **Under Signal Forms, native constraint attributes go in the schema, not the template.**
44
+ `name` on grouped radios and `maxlength` / `min` / `pattern` on a `[formField]` host raise
45
+ `NG8022` — Signal Forms owns them (F-1/F-2). (Details → `forms-and-binding.md`.)
46
+ 6. **Never theme with inline styles.** No `[style.--skra-*]` bindings or `style="…"` (breaks
47
+ CSP, ADR 021). Override tokens via a CSS class / `data-skra-*` scope; components are
48
+ `margin: 0` (ADR 022) — spacing comes from layout atoms, never a margin on the component.
49
+
50
+ ---
51
+
52
+ ## Section Router — load the ONE that matches your task
53
+
54
+ | Section | File | Load when | Status |
55
+ | --------------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | -------- |
56
+ | **Setup & gotchas** | `setup-and-gotchas.md` | First integration; import fails; which `@skra/*` package exports what; boolean-attribute & theming pitfalls | ✅ draft |
57
+ | **Forms & value binding** | `forms-and-binding.md` | Wiring an input / select / checkbox / rating to Signal Forms, Reactive Forms, or `ngModel` | ✅ draft |
58
+ | **Accessible names** | `accessible-names.md` | Naming buttons, icon-only controls, overlays; using `skra-sr-only` | ✅ draft |
59
+ | **Theming & encapsulation** | `theming-and-encapsulation.md` | Skins, dark mode, density; mounting under Shadow DOM / a microfrontend / an iframe | ✅ draft |
60
+ | **File upload** | `file-upload.md` | Choosing files (picker / drop zone), a file list with progress, the queue model, a tus upload; `accept` and language limits | ✅ draft |
61
+ | **Feedback** | `feedback.md` | Alert vs toast vs badge vs field error; `live` announcing; dismissal; an alert with a next-step action | ✅ draft |
62
+ | **Overlays** | `overlays.md` | Dialog / tooltip / popover / menu / select in the top layer; shadow & iframe limits | ✅ draft |
63
+ | **Layout** | `layout.md` | Spacing & composition with Stack / Box / Surface / Grid; building a card; the gap scale | ✅ draft |
64
+ | **Wrapping Skra** | `wrapping-patterns.md` | Building your own higher-level components over Skra atoms (simplified API) | ✅ draft |
65
+ | **Custom components** | `custom-components.md` | Authoring a net-new widget Skra does not ship (nothing to wrap) on Skra tokens — own token tier, state, promotion path | ✅ draft |
66
+
67
+ ---
68
+
69
+ ## Quick reference — task → section
70
+
71
+ | I want to… | Go to |
72
+ | ----------------------------------------------------------------------- | ----------------------------------------- |
73
+ | Add `@skra/ui/*` to a project and import an atom | `setup-and-gotchas.md` |
74
+ | Bind a text input / textarea to a form | `forms-and-binding.md` |
75
+ | Bind a `skra-select` / `skra-datepicker` / `skra-star-rating` to a form | `forms-and-binding.md` (the value bridge) |
76
+ | Name an icon-only button | `accessible-names.md` |
77
+ | Switch skin / dark mode at runtime | `theming-and-encapsulation.md` |
78
+ | Mount Skra inside a Web Component / microfrontend | `theming-and-encapsulation.md` |
79
+ | Let the user pick or drop files, show them with progress, upload them | `file-upload.md` |
80
+ | Show an alert / toast / badge, or add a "Try again" button to an alert | `feedback.md` |
81
+ | Open a dialog or tooltip | `overlays.md` |
82
+ | Space components apart | `layout.md` |
83
+ | Build a card / panel / tile (there is no `skra-card`) | `layout.md` (Surface) |
84
+ | Wrap a Skra atom in a project-specific component | `wrapping-patterns.md` |
85
+ | Build a widget Skra does not ship (scrubber, timeline, domain visual) | `custom-components.md` |
86
+ | Keep an app-local component on-skin, on-density, dark-mode-correct | `custom-components.md` |
87
+
88
+ ---
89
+
90
+ _Source of truth for the rules: Skra `FORGE_ADR_SUMMARY.md` + `adr/decisions/`. Real
91
+ consumption evidence: `apps/dogfood/FRICTION_JOURNAL.md`. This guide restates the consumer
92
+ slice of those — on divergence, the ADRs win._
@@ -0,0 +1,210 @@
1
+ # File upload — choosing files, showing them, uploading them yourself
2
+
3
+ > Section of the Skra consumer guide (added 2026-11-01, O-80). Load it when you need a file
4
+ > picker, a drop zone, or a list of files with progress. Source of truth:
5
+ > `adr/specs/component-file-upload-spec.md` + ADR 0056 Groups B and D.
6
+
7
+ Entry point: **`@skra/ui/file-upload`** — three atoms and two helpers:
8
+
9
+ | Export | What it is |
10
+ | ------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
11
+ | `SkraFileInput` — `input[type="file"][skraFileInput]` | The native control, skinned. Field wiring, drop normalisation, `filesChange` / `filesRejected`, `clear()`. |
12
+ | `SkraFileUpload` — `<skra-file-upload>` | Optional face over the input: your own action text, a drop zone, a live status. No inputs, no outputs. |
13
+ | `SkraFileItem` — `li[skraFileItem]` | One row of YOUR file list: middle-truncated name, formatted size, slots for preview / progress / error / actions. |
14
+ | `SkraFileUploadAction` / `…Instruction` / `…Status` / `…Start`, `SkraFileItemStart` / `…Detail` / `…Error` / `…End` | Slot markers — import the ones you use (projection works without them; slot DETECTION needs them). |
15
+ | `skraFormatFileSize(bytes, locale?)`, `skraFileMatchesAccept(file, accept)` | Pure helpers: SI sizes via `Intl` (`2,3 GB`), the HTML `accept` grammar. |
16
+
17
+ **Skra never uploads.** Transport (tus, multipart `fetch`, S3 presigned `PUT`), retry,
18
+ pause / resume and queue policy are yours. Skra gives you the control, the drop surface,
19
+ the rows and the two helpers.
20
+
21
+ ---
22
+
23
+ ## 1. Native atom or composite? — decide first
24
+
25
+ | You need… | Use |
26
+ | ----------------------------------------------------------- | -------------------------------------------------------------- |
27
+ | A plain file field in a form, browser wording is acceptable | `input[skraFileInput]` alone, inside `<skra-field>` |
28
+ | The button / status text **in your page's language** | `<skra-file-upload>` around the input |
29
+ | A drop zone ("or drag it here") | `<skra-file-upload>` **with** `[skra-file-upload-instruction]` |
30
+ | A compact, field-height row with your own wording | `<skra-file-upload>` **without** an instruction |
31
+ | A list of chosen / uploading files | a **sibling** `ul[skraStack]` of `li[skraFileItem]` |
32
+
33
+ **Why the composite exists.** The native control's two strings — "Choose File" and
34
+ "No file chosen" — come from the **browser's UI language, not your page's `lang`**, and no CSS
35
+ or attribute can replace them (measured in Chromium, ADR 0056 §1.6). A Polish app in an
36
+ English browser shows an English button. `<skra-file-upload>` paints its own face with your
37
+ text while the native input stays the real control — transparent, on top, covering the whole
38
+ face — so focus, keyboard, the OS picker (incl. the iOS camera / library sheet), `required`,
39
+ form submission and drops all keep working.
40
+
41
+ Even with the composite, the control's **accessibility value** stays the browser's string
42
+ ("No file chosen"); that is why your status text is linked as a description and announced.
43
+
44
+ ---
45
+
46
+ ## 2. The patterns
47
+
48
+ ### Single file in a form (`station`'s case)
49
+
50
+ ```html
51
+ <skra-field [hasError]="!!error()">
52
+ <label skraLabelFor>Session recording</label>
53
+ <skra-file-upload>
54
+ <skra-icon skra-start aria-hidden="true">…</skra-icon>
55
+ <span skra-file-upload-action>Choose a video</span>
56
+ <span skra-file-upload-status>{{ summary() }}</span>
57
+ <input skraFileInput type="file" accept="video/*" (filesChange)="onFiles($event)" (filesRejected)="onRejected($event)" />
58
+ </skra-file-upload>
59
+ <skra-hint>MP4 or MOV, up to 20 GB. Required.</skra-hint>
60
+ <skra-error>{{ error() }}</skra-error>
61
+ </skra-field>
62
+ ```
63
+
64
+ ```ts
65
+ readonly file = signal<File | null>(null);
66
+ readonly error = signal('');
67
+ readonly summary = computed(() => {
68
+ const f = this.file();
69
+ return f ? `${f.name}, ${skraFormatFileSize(f.size, 'pl-PL')}` : 'Nie wybrano nagrania';
70
+ });
71
+
72
+ onFiles(files: readonly File[]) {
73
+ const f = files[0] ?? null;
74
+ // Product policy is YOURS — Skra enforces only what the platform's picker enforces.
75
+ if (f && f.size > 20e9) { this.error.set('Plik jest większy niż 20 GB.'); return; }
76
+ this.error.set('');
77
+ this.file.set(f);
78
+ }
79
+
80
+ onRejected(rejected: readonly SkraFileRejection[]) {
81
+ const r = rejected[0];
82
+ this.error.set(
83
+ r.reason === 'type' ? `${r.file.name} nie jest plikiem wideo.`
84
+ : r.reason === 'count' ? 'Upuść jedno nagranie naraz.'
85
+ : `${r.file.name} to folder — wybierz plik.`,
86
+ );
87
+ }
88
+ ```
89
+
90
+ - The name is **"field label + your action text"** automatically (`aria-labelledby`; WCAG 2.5.3) —
91
+ **inside `<skra-field>` with `label[skraLabelFor]`**. Outside a field the action text alone names
92
+ the control, and that reference overrides a plain `<label for>` (dev mode warns).
93
+ Never put `aria-label` on the input.
94
+ - **Never project anything interactive into `<skra-file-upload>`** — the transparent input
95
+ covers the whole face and swallows the click (dev mode reports it). Buttons go in the list.
96
+
97
+ ### Many files + a list
98
+
99
+ ```html
100
+ <skra-file-upload>
101
+ <span skra-file-upload-action>Choose videos</span>
102
+ <span skra-file-upload-instruction>or drag them here</span>
103
+ <span skra-file-upload-status>{{ summary() }}</span>
104
+ <input skraFileInput type="file" accept="video/*" multiple (filesChange)="add($event)" />
105
+ </skra-file-upload>
106
+
107
+ <ul skraStack gap="sm" aria-label="Uploads">
108
+ @for (u of uploads(); track u.id) {
109
+ <li skraFileItem [name]="u.file.name" [size]="u.file.size" locale="pl-PL">
110
+ <progress skraProgress skra-file-item-detail [value]="u.sent" [max]="u.file.size" [attr.aria-label]="'Wysyłanie ' + u.file.name"></progress>
111
+ @if (u.error) { <span skra-file-item-error>{{ u.error }}</span> }
112
+ <button skraButton skra-end emphasis="low" (click)="cancel(u)">Anuluj<span skra-sr-only> {{ u.file.name }}</span></button>
113
+ </li>
114
+ }
115
+ </ul>
116
+ ```
117
+
118
+ - A row turns **invalid only while `[skra-file-item-error]` text is projected** — no `hasError`
119
+ input exists, so the colour can never appear without the words.
120
+ - Row actions: visible verb + `[skra-sr-only]` file name (ADR 033 Path 2). The progress bar has no
121
+ text, so it takes `aria-label` (Path 3).
122
+ - Previews are your content: put an `<img [src]="objectUrl">` in `[skra-start]`
123
+ (`URL.createObjectURL`, and `revokeObjectURL` when the row goes).
124
+
125
+ ### The queue model — `clear()` so the same file can be picked again
126
+
127
+ A file input fires no `change` when the user picks the file that is already selected. In a
128
+ queue (pick → enqueue → pick again) clear the native selection after taking the files:
129
+
130
+ ```html
131
+ <input skraFileInput type="file" multiple #picker (filesChange)="enqueue($event); picker.clear()" />
132
+ ```
133
+
134
+ `clear()` emits nothing. `#picker` resolves to the `SkraFileInput` component.
135
+
136
+ ### A resumable upload with tus (multi-GB, `station`)
137
+
138
+ Skra hands you `File`s; the transport is yours. With `tus-js-client` (your dependency):
139
+
140
+ ```ts
141
+ import { Upload } from 'tus-js-client';
142
+
143
+ start(file: File) {
144
+ const row = { id: crypto.randomUUID(), file, sent: 0, error: '' };
145
+ this.uploads.update((all) => [...all, row]);
146
+ const upload = new Upload(file, {
147
+ endpoint: '/api/uploads',
148
+ chunkSize: 50 * 1024 * 1024,
149
+ retryDelays: [0, 3000, 10000, 30000],
150
+ metadata: { filename: file.name, filetype: file.type },
151
+ onProgress: (sent) => this.patch(row.id, { sent }),
152
+ onError: () => this.patch(row.id, { error: 'Połączenie przerwane. Spróbuj ponownie.' }),
153
+ onSuccess: () => this.patch(row.id, { sent: file.size }),
154
+ });
155
+ upload.findPreviousUploads().then((prev) => {
156
+ if (prev.length) upload.resumeFromPreviousUpload(prev[0]);
157
+ upload.start();
158
+ });
159
+ }
160
+ ```
161
+
162
+ While any `<skra-file-upload>` is mounted, a file dropped a few pixels **outside** the zone is
163
+ cancelled instead of making the browser navigate to it (which would abort the upload). Text and
164
+ link drags, any native `<input type="file">` and editable content are left alone.
165
+
166
+ ---
167
+
168
+ ## 3. Rules and limits you must know
169
+
170
+ 1. **`accept` is never a guarantee.** Every OS lets the user bypass it ("All files", Android
171
+ pickers). Skra re-checks picks AND drops against it (`filesRejected` reason `type`), but the
172
+ **server must validate** type and size.
173
+ 2. **Skra enforces only what the platform's picker enforces**: `type` (accept), `count` (several
174
+ files on a non-`multiple` input — ALL are rejected, never one of N), `directory` (a dropped
175
+ folder). Size limits, a maximum count, duplicates — yours, in `filesChange`.
176
+ 3. **Typeless files** (`File.type === ''`, common for `.mkv` / `.heic`): matched by extension
177
+ tokens only, and **accepted** when `accept` lists only MIME types. Add `.mkv` etc. to `accept`
178
+ if you want a strict list.
179
+ 4. **Order: `filesChange` first, `filesRejected` last** — on both paths. Clear your error in
180
+ `onFiles` and set it in `onRejected`, and a mixed selection ends on the message. A selection where
181
+ every file was rejected emits only `filesRejected`.
182
+ 5. **`webkitdirectory`** hands over every file of the folder and counts as multiple — no `count`
183
+ rejection, even without `multiple`.
184
+ 6. **Native `(change)` vs `filesChange`.** `(change)` still fires and reports the RAW selection;
185
+ `filesChange` is the normalised one. A drop that is fully rejected changes nothing (no
186
+ `change`, only `filesRejected`).
187
+ 7. **"Required" does not reach screen readers on a file input in Chromium.** The control is
188
+ exposed as a `button`, which carries neither `aria-required` nor native `required` (measured).
189
+ Skra still sets `aria-required` from `<skra-field required>`; **also say "required" in visible
190
+ text** — the label or the hint.
191
+ 8. **Readonly** (`<skra-field readonly>`): native `readonly` does not apply to file inputs, and
192
+ `aria-readonly` is silent for a `button`, so the control is `aria-disabled` (still focusable,
193
+ announced "unavailable"); the picker and drops are blocked.
194
+ 9. **The native atom alone shows the browser's words** — fine for internal tools; use the
195
+ composite when the page language matters. In a narrow container the browser also CLIPS its
196
+ own status text ("No file ch…") — Skra cannot wrap a user-agent internal. The composite
197
+ wraps instead: its status moves below the action, and a file row's actions move below the
198
+ name, whenever they cannot share a line with at least half of it.
199
+ 10. **Inputs you will NOT find**: `accept`, `multiple`, `capture`, `required`, `disabled` are the
200
+ native attributes on your `<input>` — Skra reads them, never mirrors them. No `ariaLabel`, no
201
+ `maxSize`, no upload URL.
202
+
203
+ ---
204
+
205
+ ## 4. Verification status
206
+
207
+ Built and verified on Chromium, Firefox and desktop WebKit by automated tests (the picker opens
208
+ from every point of the face, drop normalisation, drag-over, height parity at every density,
209
+ the accessibility tree in Chromium, forced colours in Chromium). **Real-device iOS Safari,
210
+ Android and screen-reader passes are still open (O-81)** — see the gate table in the spec.
@@ -10,43 +10,44 @@ will break on any internal refactor). Skra ships as **one package, `@skra/ui`**,
10
10
  secondary entry point per component (ADR 0054 / O-31 consolidation). The primary `@skra/ui`
11
11
  entry exports nothing — always import from a `@skra/ui/<entry>` subpath.
12
12
 
13
- | Entry point | Exports (representative) |
14
- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
15
- | `@skra/ui/button` | `button[skraButton]` |
16
- | `@skra/ui/link` | `a[skraLink]` |
17
- | `@skra/ui/icon` | icon container atom |
18
- | `@skra/ui/typography` | `[skraTypography]` |
19
- | `@skra/ui/layout` | `[skraStack]`, `[skraBox]`, `[skraGrid]`, `[skraSurface]` |
20
- | `@skra/ui/kbd` | `kbd[skraKbd]` |
13
+ | Entry point | Exports (representative) |
14
+ | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
15
+ | `@skra/ui/button` | `button[skraButton]` |
16
+ | `@skra/ui/link` | `a[skraLink]` |
17
+ | `@skra/ui/icon` | icon container atom |
18
+ | `@skra/ui/typography` | `[skraTypography]` |
19
+ | `@skra/ui/layout` | `[skraStack]`, `[skraBox]`, `[skraGrid]`, `[skraSurface]` |
20
+ | `@skra/ui/kbd` | `kbd[skraKbd]` |
21
21
  | `@skra/ui/field` | `<skra-field>`, `[skraLabel]`, `[skraHint]`, `[skraError]`, `fieldset[skraFieldset]`, `legend[skraLegend]`, `SkraFieldContext` |
22
- | `@skra/ui/input` | `input[skraInput]` |
23
- | `@skra/ui/textarea` | `textarea[skraTextarea]`, auto-resize |
24
- | `@skra/ui/checkbox` | `input[skraCheckbox]` |
25
- | `@skra/ui/radio` | `input[skraRadio]` |
26
- | `@skra/ui/switch` | `input[skraSwitch]` |
27
- | `@skra/ui/counter` | `skra-counter` |
28
- | `@skra/ui/star-rating` | `skra-star-rating` |
29
- | `@skra/ui/native-select` | `select[skraSelect]` (`SkraNativeSelect`) |
30
- | `@skra/ui/dialog` | `dialog[skraDialog]` |
31
- | `@skra/ui/tooltip` | `[skraTooltip]` |
32
- | `@skra/ui/popover` | `skra-popover` |
33
- | `@skra/ui/menu` | `skra-menu`, `skra-menu-item` |
34
- | `@skra/ui/select` | `skra-select` (+ `SkraOption` & option directives, re-exported) |
35
- | `@skra/ui/multi-select` | `skra-multi-select` (+ `SkraOption` & option directives, re-exported) |
36
- | `@skra/ui/combobox` | `skra-combobox` (+ `SkraOption` & option directives, re-exported) |
37
- | `@skra/ui/datepicker` | `skra-datepicker` |
38
- | `@skra/ui/alert` | `skra-alert` |
39
- | `@skra/ui/badge` | `skra-badge` |
40
- | `@skra/ui/chip` | `skra-chip` |
41
- | `@skra/ui/toast` | `skra-toast`, `skra-toast-region` |
42
- | `@skra/ui/tabs` | `[skraTabs]`, `[skraTablist]`, `button[skraTab]`, `[skraTabPanel]` |
43
- | `@skra/ui/accordion` | `[skraAccordion]` family |
44
- | `@skra/ui/progress` | `progress[skraProgress]` |
45
- | `@skra/ui/skeleton` | `skra-skeleton` |
46
- | `@skra/ui/loading-indicator` | `[skraLoadingIndicator]` |
47
- | `@skra/ui/avatar` | `skra-avatar` |
48
- | `@skra/ui/status-dot` | `skra-status-dot` |
49
- | `@skra/ui/rating-display` | `skra-rating-display` |
22
+ | `@skra/ui/input` | `input[skraInput]` |
23
+ | `@skra/ui/textarea` | `textarea[skraTextarea]`, auto-resize |
24
+ | `@skra/ui/checkbox` | `input[skraCheckbox]` |
25
+ | `@skra/ui/radio` | `input[skraRadio]` |
26
+ | `@skra/ui/switch` | `input[skraSwitch]` |
27
+ | `@skra/ui/counter` | `skra-counter` |
28
+ | `@skra/ui/star-rating` | `skra-star-rating` |
29
+ | `@skra/ui/file-upload` | `input[skraFileInput]`, `skra-file-upload`, `li[skraFileItem]`, `skraFormatFileSize`, `skraFileMatchesAccept` |
30
+ | `@skra/ui/native-select` | `select[skraSelect]` (`SkraNativeSelect`) |
31
+ | `@skra/ui/dialog` | `dialog[skraDialog]` |
32
+ | `@skra/ui/tooltip` | `[skraTooltip]` |
33
+ | `@skra/ui/popover` | `skra-popover` |
34
+ | `@skra/ui/menu` | `skra-menu`, `skra-menu-item` |
35
+ | `@skra/ui/select` | `skra-select` (+ `SkraOption` & option directives, re-exported) |
36
+ | `@skra/ui/multi-select` | `skra-multi-select` (+ `SkraOption` & option directives, re-exported) |
37
+ | `@skra/ui/combobox` | `skra-combobox` (+ `SkraOption` & option directives, re-exported) |
38
+ | `@skra/ui/datepicker` | `skra-datepicker` |
39
+ | `@skra/ui/alert` | `skra-alert` |
40
+ | `@skra/ui/badge` | `skra-badge` |
41
+ | `@skra/ui/chip` | `skra-chip` |
42
+ | `@skra/ui/toast` | `skra-toast`, `skra-toast-region` |
43
+ | `@skra/ui/tabs` | `[skraTabs]`, `[skraTablist]`, `button[skraTab]`, `[skraTabPanel]` |
44
+ | `@skra/ui/accordion` | `[skraAccordion]` family |
45
+ | `@skra/ui/progress` | `progress[skraProgress]` |
46
+ | `@skra/ui/skeleton` | `skra-skeleton` |
47
+ | `@skra/ui/loading-indicator` | `[skraLoadingIndicator]` |
48
+ | `@skra/ui/avatar` | `skra-avatar` |
49
+ | `@skra/ui/status-dot` | `skra-status-dot` |
50
+ | `@skra/ui/rating-display` | `skra-rating-display` |
50
51
 
51
52
  > `@skra/ui/core` exists for cross-component shared satellites (`SkraOption` & option
52
53
  > directives, `SkraSelectTrigger`, `SKRA_LISTBOX_PANEL`). Prefer the re-exports from