@reliability-design/web 1.4.9 → 1.4.10

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 (54) hide show
  1. package/CHANGELOG.md +155 -2
  2. package/custom-elements.json +18600 -0
  3. package/dist/components/rel-accordion/README.md +9 -2
  4. package/dist/components/rel-alert/README.md +4 -7
  5. package/dist/components/rel-autocomplete/README.md +209 -209
  6. package/dist/components/rel-avatar/README.md +228 -118
  7. package/dist/components/rel-breadcrumb/README.md +1 -0
  8. package/dist/components/rel-button/README.md +9 -9
  9. package/dist/components/rel-calendar/README.md +111 -109
  10. package/dist/components/rel-cardmedia/README.md +1 -1
  11. package/dist/components/rel-carousel/README.md +187 -187
  12. package/dist/components/rel-checkbox/README.md +159 -147
  13. package/dist/components/rel-chip/README.md +141 -138
  14. package/dist/components/rel-container/README.md +1 -1
  15. package/dist/components/rel-date-picker/README.md +221 -206
  16. package/dist/components/rel-drawer/README.md +4 -4
  17. package/dist/components/rel-kbd/README.md +188 -141
  18. package/dist/components/rel-layout/README.md +34 -34
  19. package/dist/components/rel-menu/README.md +17 -10
  20. package/dist/components/rel-menubar/README.md +218 -218
  21. package/dist/components/rel-otp/README.md +4 -8
  22. package/dist/components/rel-pagination/README.md +141 -100
  23. package/dist/components/rel-popover/README.md +220 -203
  24. package/dist/components/rel-radio/README.md +142 -131
  25. package/dist/components/rel-range-picker/README.md +194 -171
  26. package/dist/components/rel-resizable/README.md +220 -199
  27. package/dist/components/rel-segmented-button/README.md +158 -157
  28. package/dist/components/rel-select/README.md +20 -17
  29. package/dist/components/rel-sheet/README.md +3 -2
  30. package/dist/components/rel-signature-pad/README.md +161 -0
  31. package/dist/components/rel-slider/README.md +140 -131
  32. package/dist/components/rel-snackbar/README.md +170 -168
  33. package/dist/components/rel-split-button/README.md +129 -0
  34. package/dist/components/rel-stepper/README.md +171 -171
  35. package/dist/components/rel-switch/README.md +122 -117
  36. package/dist/components/rel-tab/README.md +184 -174
  37. package/dist/components/rel-textfield/README.md +21 -28
  38. package/dist/components/rel-time-picker/README.md +188 -170
  39. package/dist/components/rel-toast/README.md +20 -11
  40. package/dist/components/rel-tooltip/README.md +5 -4
  41. package/dist/components/rel-tree/README.md +157 -0
  42. package/dist/components/rel-upload/README.md +8 -5
  43. package/dist/index.d.mts +1602 -624
  44. package/dist/index.d.ts +1602 -624
  45. package/dist/index.js +11755 -6673
  46. package/dist/index.js.map +1 -1
  47. package/dist/index.mjs +10955 -5880
  48. package/dist/index.mjs.map +1 -1
  49. package/dist/styles/motion.css +31 -0
  50. package/dist/styles/motion.tokens.json +38 -0
  51. package/dist/styles/vars.css +88 -64
  52. package/dist/{vars-DXX6QOAG.css → vars-E22LYFMV.css} +88 -64
  53. package/package.json +60 -37
  54. package/dist/styles/focus-ring.ts +0 -84
@@ -1,203 +1,220 @@
1
- # rel-popover
2
-
3
- A click-to-open floating panel anchored to a trigger, implementing the Figma "Popover" component (including its opt-in chevron). The folder registers two elements: `<rel-popover>` is the controller — its first child is the trigger, its `content` slot is the panel — and `<rel-popover-content>` is the optional styled surface (neutral background, border, radius, shadow, S/M/L padding) you drop into that slot. The panel is promoted to the browser top layer and positioned in viewport coordinates, so it renders correctly even inside a centred `<rel-modal>`. Use it for rich, interactive content that needs an explicit click — small forms, filters, menus, detail cards. For a short hover label use `<rel-tooltip>`; for a task that must be completed before continuing use `<rel-modal>`.
4
-
5
- ## Usage
6
-
7
- ```html
8
- <!-- Basic: first child = trigger, slot="content" = panel -->
9
- <rel-popover>
10
- <rel-button>Open</rel-button>
11
- <rel-popover-content aria-label="About popovers">
12
- <h3>Popover title</h3>
13
- <p>Any HTML can go here.</p>
14
- </rel-popover-content>
15
- </rel-popover>
16
-
17
- <!-- Panel padding (Figma Size S / M / L = 12 / 16 / 20px) -->
18
- <rel-popover>
19
- <rel-button variant="secondary">Compact</rel-button>
20
- <rel-popover-content size="small" aria-label="Compact panel"><p>12px padding</p></rel-popover-content>
21
- </rel-popover>
22
- <rel-popover>
23
- <rel-button variant="secondary">Roomy</rel-button>
24
- <rel-popover-content size="large" aria-label="Roomy panel"><p>20px padding</p></rel-popover-content>
25
- </rel-popover>
26
-
27
- <!-- Positioning: side, alignment and pixel offsets -->
28
- <rel-popover side="top" align="start">
29
- <rel-button>Top / start</rel-button>
30
- <rel-popover-content aria-label="Top start"><p>Above the trigger, left edges aligned.</p></rel-popover-content>
31
- </rel-popover>
32
- <rel-popover side="right" sideoffset="16" alignoffset="8">
33
- <rel-button>Right, 16px away</rel-button>
34
- <rel-popover-content aria-label="Right offset"><p>16px gap, shifted 8px down.</p></rel-popover-content>
35
- </rel-popover>
36
-
37
- <!-- Chevron pointing at the trigger -->
38
- <rel-popover side="bottom" arrow>
39
- <rel-button>With arrow</rel-button>
40
- <rel-popover-content aria-label="Arrow demo"><p>The chevron follows the trigger centre.</p></rel-popover-content>
41
- </rel-popover>
42
-
43
- <!-- Any element can be the trigger or the panel -->
44
- <rel-popover>
45
- <span>Plain text trigger (made focusable automatically)</span>
46
- <div slot="content" style="padding: 12px; background: var(--rel-bg-neutral); border: 1px solid var(--rel-border-default); border-radius: 8px;">
47
- Unstyled custom panel
48
- </div>
49
- </rel-popover>
50
-
51
- <!-- Form with focus trap: Tab cycles inside the panel until it closes -->
52
- <rel-popover trapfocus>
53
- <rel-button>Edit profile</rel-button>
54
- <rel-popover-content aria-label="Edit profile">
55
- <h3>Edit profile</h3>
56
- <form>
57
- <rel-textfield label="Name" value="Ada Lovelace"></rel-textfield>
58
- <rel-textfield label="Email" type="email" value="ada@example.com"></rel-textfield>
59
- <rel-button>Save</rel-button>
60
- </form>
61
- </rel-popover-content>
62
- </rel-popover>
63
-
64
- <!-- Controlled: you own `open` and apply every requested change -->
65
- <rel-popover id="controlled" open>
66
- <rel-button>Controlled trigger</rel-button>
67
- <rel-popover-content aria-label="Controlled panel">
68
- <p>State lives outside the component.</p>
69
- <rel-button id="done" variant="secondary">Done</rel-button>
70
- </rel-popover-content>
71
- </rel-popover>
72
- <script>
73
- const popover = document.getElementById('controlled');
74
- popover.addEventListener('rel-open-change', (e) => {
75
- popover.open = e.detail.open; // nothing happens until you apply it
76
- });
77
- document.getElementById('done').addEventListener('click', () => {
78
- popover.open = false;
79
- });
80
- // Uncontrolled instances: never set `open`; start open with `defaultopen` instead.
81
- // Default-true booleans are switched off via the property:
82
- popover.closeOnOutsideClick = false;
83
- popover.closeOnEscape = false;
84
- </script>
85
- ```
86
-
87
- <!-- readme-gen:start -->
88
-
89
- <!-- Generated from rel-popover.ts, rel-popover-content.ts — do not edit; run readme-generate.mjs -->
90
-
91
- ## Elements
92
-
93
- | Element | Class | Source |
94
- | --- | --- | --- |
95
- | `<rel-popover>` | `RelPopover` | `rel-popover.ts` |
96
- | `<rel-popover-content>` | `RelPopoverContent` | `rel-popover-content.ts` |
97
-
98
- ## API
99
-
100
- ### `<rel-popover>`
101
-
102
- #### Attributes / Properties
103
-
104
- | Property | Attribute | Type | Default | Description |
105
- | --- | --- | --- | --- | --- |
106
- | `open` | `open` | `boolean` | `undefined` | Controlled open state. Leave unset for uncontrolled mode; once set (true or false) the component only emits `rel-open-change` and you must update `open` yourself. Reflected. |
107
- | `defaultOpen` | `defaultopen` | `boolean` | `false` | Initial state in uncontrolled mode (attribute `defaultopen`); ignored when `open` is set. |
108
- | `disabled` | `disabled` | `boolean` | `false` | Blocks trigger interaction and dims the host to 60% opacity; does not close an already-open panel. Reflected. |
109
- | `side` | `side` | `"top" \| "right" \| "bottom" \| "left"` | `"bottom"` | Which side of the trigger the panel opens on: `top`, `right`, `bottom` (default) or `left`. The panel is clamped 8px inside the viewport but never flips to another side. Reflected. |
110
- | `align` | `align` | `"start" \| "center" \| "end"` | `"center"` | Alignment along the cross axis: `start` (left / top edge of the trigger), `center` (default) or `end` (right / bottom edge). Reflected. |
111
- | `sideOffset` | `sideoffset` | `number` | `8` | Gap between trigger and panel in pixels (attribute `sideoffset`, default 8). With `arrow` the effective gap is at least 13px. |
112
- | `alignOffset` | `alignoffset` | `number` | `0` | Extra shift along the alignment axis in pixels (attribute `alignoffset`, default 0); positive moves right / down. |
113
- | `trapFocus` | `trapfocus` | `boolean` | `false` | Keep Tab / Shift+Tab cycling inside the panel while open (attribute `trapfocus`); use for forms. |
114
- | `closeOnEscape` | `closeonescape` | `boolean` | `true` | Close on Escape (attribute `closeonescape`, default true; switch off via the property, e.g. `el.closeOnEscape = false`). |
115
- | `closeOnOutsideClick` | `closeonoutsideclick` | `boolean` | `true` | Close on a pointer-down outside the trigger and panel (attribute `closeonoutsideclick`, default true; switch off via the property). Also lets another popover's opening dismiss this one. |
116
- | `arrow` | `arrow` | `boolean` | `false` | Opt-in pointer (audit 23a): a 14×11 triangle on the panel edge that faces the trigger — the Figma Popover "Chevron" — following `side` and tracking the trigger's centre along the cross axis (clamped inside the panel). It is drawn with the panel's tokens (`--rel-popover-arrow-bg` / `--rel-popover-arrow-border`, defaulting to rel-popover-content's background and border). With the arrow on, the panel keeps at least the arrow's height plus 2px away from the trigger so the tip never overlaps it; larger `sideOffset` values still apply. Default off — existing popovers render exactly as before. Reflected. |
117
-
118
- ### Events
119
-
120
- | Event | Detail | Bubbles / composed | Description |
121
- | --- | --- | --- | --- |
122
- | `rel-open-change` | `{ open: next }` | bubbles / composed | Fired whenever the trigger, Escape, an outside click or another popover requests a state change. Detail: `{ open: boolean }` — the requested state; in controlled mode apply it to `open` yourself. |
123
-
124
- ### Slots
125
-
126
- | Slot | Description |
127
- | --- | --- |
128
- | (default) | The trigger: the first slotted element toggles the panel on pointer down / Enter / Space and receives `aria-haspopup`, `aria-expanded` and `aria-controls` (a bare `<div>` / `<span>` also gets `tabindex="0"` and `role="button"`). |
129
- | `content` | The panel body, usually a `<rel-popover-content>`; rendered inside the fixed, top-layer wrapper only while open. |
130
-
131
- ### CSS Parts
132
-
133
- | Part | Description |
134
- | --- | --- |
135
- | `arrow` | The 14×14 chevron box rendered on the panel edge facing the trigger when `arrow` is set (absent otherwise). |
136
-
137
- ### Tokens / CSS Variables
138
-
139
- #### Component hooks
140
-
141
- Set these on the host (or an ancestor) to customise the element without touching design tokens.
142
-
143
- | Custom property | Default | Description |
144
- | --- | --- | --- |
145
- | `--rel-popover-arrow-bg` | `var(--rel-bg-neutral)` | Fill of the `arrow` chevron (default `--rel-bg-neutral`, matching `<rel-popover-content>`). |
146
- | `--rel-popover-arrow-border` | `transparent` | **Deprecated.** Stroke of the `arrow` chevron's two slanted edges. The panel no longer draws a border — overlapping surfaces are separated by shadow — so this defaults to `transparent` and the chevron has no edge. Set it to restore a stroke. |
147
-
148
- ### `<rel-popover-content>`
149
-
150
- #### Attributes / Properties
151
-
152
- | Property | Attribute | Type | Default | Description |
153
- | --- | --- | --- | --- | --- |
154
- | `ariaLabel` | `aria-label` | `string` | `""` | Accessible name applied to the inner `role="dialog"` box (attribute `aria-label`); describe what the panel is for. |
155
- | `size` | `size` | `"small" \| "medium" \| "large" \| "sm" \| "md" \| "lg"` | `"medium"` | Figma Size axis: panel padding 12px (`small`), 16px (`medium`, default) or 20px (`large`). `sm` / `md` / `lg` are alternative spellings and stay supported — they are normalised to the full words, not deprecated. Reflected. |
156
-
157
- ### Events
158
-
159
- | Event | Detail | Bubbles / composed | Description |
160
- | --- | --- | --- | --- |
161
- | — | — | — | This element emits no custom events. |
162
-
163
- ### Slots
164
-
165
- | Slot | Description |
166
- | --- | --- |
167
- | (default) | The panel content (headings, paragraphs, form controls, buttons). |
168
-
169
- ### CSS Parts
170
-
171
- | Part | Description |
172
- | --- | --- |
173
- | `content` | The padded `role="dialog"` box that carries the surface, border, shadow and size padding. |
174
-
175
- <!-- readme-gen:end -->
176
-
177
- ## Accessibility
178
-
179
- - The trigger (first slotted element) is decorated with `aria-haspopup="dialog"`, `aria-expanded` (kept in sync with the open state) and `aria-controls` pointing at the panel wrapper. A trigger that is not natively focusable and contains nothing focusable (a bare `<div>` / `<span>`) also gets `tabindex="0"` and `role="button"`; native buttons and `<rel-button>` are left alone so they do not get a second Tab stop.
180
- - `<rel-popover-content>` renders its box with `role="dialog"`; pass `aria-label` so the dialog has a name. A custom panel element should carry its own role and label.
181
- - On open, focus moves to the first focusable element inside the panel (links, enabled buttons and inputs, positive `tabindex`), or to the panel wrapper when there is none. On close, focus returns to whatever was focused before opening (normally the trigger).
182
- - With `trapfocus`, Tab from the last focusable element wraps to the first and Shift+Tab from the first wraps to the last, so keyboard focus cannot leave the panel while it is open. Without it, Tab leaves the panel normally and the panel stays open until an outside click or Escape.
183
- - There are no screen-reader announcements beyond the `aria-expanded` change; put the important content first in the panel so it is read when focus lands.
184
-
185
- | Key | Action |
186
- | --- | --- |
187
- | `Enter` / `Space` (on the trigger) | Toggle the panel |
188
- | `Escape` (anywhere, while open) | Close (unless `closeOnEscape` is switched off) |
189
- | `Tab` / `Shift+Tab` | Move through the panel's focusable elements; with `trapfocus` the order wraps at both ends |
190
-
191
- ## Notes / Edge Cases
192
-
193
- - **Slots.** The trigger is the *default* slot's first element; the panel goes in `slot="content"`. There is no `trigger` slot. Only one element should be in the default slot — extra default-slot children render next to the trigger but never toggle the panel.
194
- - **Top layer / inside a modal.** The panel wrapper is a native `popover="manual"` element shown with `showPopover()`, so a transformed or `overflow: hidden` ancestor (for example `<rel-modal>`'s centred panel) cannot clip it or break its `position: fixed` coordinates. Where the Popover API is unavailable it falls back to `position: fixed; z-index: 1001` and re-measures once to cancel any containing-block offset.
195
- - **No collision flipping.** The panel always opens on the requested `side`; it is only clamped to stay 8px inside the viewport. Pick the side yourself for edge-of-screen triggers.
196
- - **One open at a time.** Opening a popover light-dismisses every other open popover that honours `closeOnOutsideClick`, whether it was opened by pointer, keyboard or code. A popover nested inside another's panel keeps its parent open.
197
- - **Controlled vs uncontrolled.** `open` is `undefined` until you set it; setting it (to `true` *or* `false`) switches the instance to controlled mode permanently, after which the component only emits `rel-open-change` and never changes its own state. Use `defaultopen` for an uncontrolled popover that starts open.
198
- - **Default-true booleans** (`closeOnEscape`, `closeOnOutsideClick`) cannot be turned off with an attribute — a boolean attribute is true whenever present. Set the property instead.
199
- - **Positioning updates** run on window resize and on scroll (capture phase). They do not run when the panel's own content changes size; toggle `open` or trigger a resize if you swap content while open.
200
- - **Arrow.** `arrow` draws a 14×11 chevron on the panel edge facing the trigger, centred on the trigger and clamped 15px in from the panel corners; the trigger gap becomes `max(sideoffset, 13px)`. Colour it with `--rel-popover-arrow-bg` / `--rel-popover-arrow-border` (defaults match `<rel-popover-content>`); if you use a custom panel with a different surface, set both so the chevron blends in.
201
- - **`<rel-popover-content>`** is `display: block; width: 100%; max-width: 400px`; override `max-width` on the element for wider panels. Slotted `h1`–`h6` get heading-sm type and `p` gets placeholder-tone body text with a bottom margin (none on the last paragraph).
202
- - **`disabled`** blocks the trigger and dims the host but does not close a panel that is already open — set `open = false` first if needed.
203
- - The `size` values on `<rel-popover-content>` are `small` / `medium` / `large`. The short spellings `sm` / `md` / `lg` are alternative spellings and keep working — they are normalised to the full words before the attribute reflects, so `rel-popover-content[size="medium"]` matches whichever spelling was written.
1
+ # rel-popover
2
+
3
+ A click-to-open floating panel anchored to a trigger, implementing the Figma "Popover" component (including its opt-in chevron). The folder registers two elements: `<rel-popover>` is the controller — its first child is the trigger, its `content` slot is the panel — and `<rel-popover-content>` is the optional styled surface (neutral background, radius, shadow, S/M/L padding; no border) you drop into that slot. The panel is promoted to the browser top layer and positioned in viewport coordinates, so it renders correctly even inside a centred `<rel-modal>`. Use it for rich, interactive content that needs an explicit click — small forms, filters, menus, detail cards. For a short hover label use `<rel-tooltip>`; for a task that must be completed before continuing use `<rel-modal>`.
4
+
5
+ ## Usage
6
+
7
+ ```html
8
+ <!-- Basic: first child = trigger, slot="content" = panel -->
9
+ <rel-popover>
10
+ <rel-button>Open</rel-button>
11
+ <rel-popover-content aria-label="About popovers">
12
+ <h3>Popover title</h3>
13
+ <p>Any HTML can go here.</p>
14
+ </rel-popover-content>
15
+ </rel-popover>
16
+
17
+ <!-- Panel padding (Figma Size S / M / L = 12 / 16 / 20px) -->
18
+ <rel-popover>
19
+ <rel-button variant="secondary">Compact</rel-button>
20
+ <rel-popover-content size="small" aria-label="Compact panel"><p>12px padding</p></rel-popover-content>
21
+ </rel-popover>
22
+ <rel-popover>
23
+ <rel-button variant="secondary">Roomy</rel-button>
24
+ <rel-popover-content size="large" aria-label="Roomy panel"><p>20px padding</p></rel-popover-content>
25
+ </rel-popover>
26
+
27
+ <!-- Positioning: side, alignment and pixel offsets -->
28
+ <rel-popover side="top" align="start">
29
+ <rel-button>Top / start</rel-button>
30
+ <rel-popover-content aria-label="Top start"><p>Above the trigger, left edges aligned.</p></rel-popover-content>
31
+ </rel-popover>
32
+ <rel-popover side="right" side-offset="16" align-offset="8">
33
+ <rel-button>Right, 16px away</rel-button>
34
+ <rel-popover-content aria-label="Right offset"><p>16px gap, shifted 8px down.</p></rel-popover-content>
35
+ </rel-popover>
36
+
37
+ <!-- Chevron pointing at the trigger -->
38
+ <rel-popover side="bottom" arrow>
39
+ <rel-button>With arrow</rel-button>
40
+ <rel-popover-content aria-label="Arrow demo"><p>The chevron follows the trigger centre.</p></rel-popover-content>
41
+ </rel-popover>
42
+
43
+ <!-- Any element can be the trigger or the panel -->
44
+ <rel-popover>
45
+ <span>Plain text trigger (made focusable automatically)</span>
46
+ <div slot="content" style="padding: 12px; background: var(--rel-bg-neutral); border: 1px solid var(--rel-border-default); border-radius: 8px;">
47
+ Unstyled custom panel
48
+ </div>
49
+ </rel-popover>
50
+
51
+ <!-- Form with focus trap: Tab cycles inside the panel until it closes -->
52
+ <rel-popover trap-focus>
53
+ <rel-button>Edit profile</rel-button>
54
+ <rel-popover-content aria-label="Edit profile">
55
+ <h3>Edit profile</h3>
56
+ <form>
57
+ <rel-textfield label="Name" value="Ada Lovelace"></rel-textfield>
58
+ <rel-textfield label="Email" type="email" value="ada@example.com"></rel-textfield>
59
+ <rel-button>Save</rel-button>
60
+ </form>
61
+ </rel-popover-content>
62
+ </rel-popover>
63
+
64
+ <!-- Controlled: you own `open` and apply every requested change -->
65
+ <rel-popover id="controlled" open>
66
+ <rel-button>Controlled trigger</rel-button>
67
+ <rel-popover-content aria-label="Controlled panel">
68
+ <p>State lives outside the component.</p>
69
+ <rel-button id="done" variant="secondary">Done</rel-button>
70
+ </rel-popover-content>
71
+ </rel-popover>
72
+ <script>
73
+ const popover = document.getElementById('controlled');
74
+ popover.addEventListener('rel-open-change', (e) => {
75
+ popover.open = e.detail.open; // nothing happens until you apply it
76
+ });
77
+ document.getElementById('done').addEventListener('click', () => {
78
+ popover.open = false;
79
+ });
80
+ // Uncontrolled instances: never set `open`; start open with `default-open` instead.
81
+ // Default-true booleans are switched off via the property:
82
+ popover.closeOnOutsideClick = false;
83
+ popover.closeOnEscape = false;
84
+ </script>
85
+ ```
86
+
87
+ ### Deprecated aliases
88
+
89
+ ```html
90
+ <!-- Lowercase-joined attribute spellings → kebab-case (since 1.5.0; still work, log a one-time
91
+ console notice, removed in 3.0) -->
92
+ <rel-popover defaultopen sideoffset="16" alignoffset="8" trapfocus closeonescape="false" closeonoutsideclick="false">…</rel-popover>
93
+ <!-- canonical -->
94
+ <rel-popover default-open side-offset="16" align-offset="8" trap-focus close-on-escape="false" close-on-outside-click="false">…</rel-popover>
95
+ ```
96
+
97
+ <!-- readme-gen:start -->
98
+
99
+ <!-- Generated from rel-popover.ts, rel-popover-content.ts — do not edit; run readme-generate.mjs -->
100
+
101
+ ## Elements
102
+
103
+ | Element | Class | Source |
104
+ | --- | --- | --- |
105
+ | `<rel-popover>` | `RelPopover` | `rel-popover.ts` |
106
+ | `<rel-popover-content>` | `RelPopoverContent` | `rel-popover-content.ts` |
107
+
108
+ ## API
109
+
110
+ ### `<rel-popover>`
111
+
112
+ #### Attributes / Properties
113
+
114
+ | Property | Attribute | Type | Default | Description |
115
+ | --- | --- | --- | --- | --- |
116
+ | `open` | `open` | `boolean` | `undefined` | Controlled open state. Leave unset for uncontrolled mode; once set (true or false) the component only emits `rel-open-change` and you must update `open` yourself. Reflected. |
117
+ | `defaultOpen` | `default-open` | `boolean` | `false` | Initial state in uncontrolled mode (attribute `default-open`); ignored when `open` is set. The `defaultopen` attribute spelling is deprecated (still works; removal targeted for 3.0). |
118
+ | `disabled` | `disabled` | `boolean` | `false` | Blocks trigger interaction and dims the host to 60% opacity; does not close an already-open panel. Reflected. |
119
+ | `side` | `side` | `"top" \| "right" \| "bottom" \| "left"` | `"bottom"` | Which side of the trigger the panel opens on: `top`, `right`, `bottom` (default) or `left`. The panel is clamped 8px inside the viewport but never flips to another side. Reflected. |
120
+ | `align` | `align` | `"start" \| "center" \| "end"` | `"center"` | Alignment along the cross axis: `start` (left / top edge of the trigger), `center` (default) or `end` (right / bottom edge). Reflected. |
121
+ | `sideOffset` | `side-offset` | `number` | `8` | Gap between trigger and panel in pixels (attribute `side-offset`, default 8). With `arrow` the effective gap is at least 13px. The `sideoffset` attribute spelling is deprecated (still works; removal targeted for 3.0). |
122
+ | `alignOffset` | `align-offset` | `number` | `0` | Extra shift along the alignment axis in pixels (attribute `align-offset`, default 0); positive moves right / down. The `alignoffset` attribute spelling is deprecated (still works; removal targeted for 3.0). |
123
+ | `trapFocus` | `trap-focus` | `boolean` | `false` | Keep Tab / Shift+Tab cycling inside the panel while open (attribute `trap-focus`); use for forms. The `trapfocus` attribute spelling is deprecated (still works; removal targeted for 3.0). |
124
+ | `closeOnEscape` | `close-on-escape` | `boolean` | `true` | Close on Escape (attribute `close-on-escape`, default true; switch off with `close-on-escape="false"` or `el.closeOnEscape = false`). The `closeonescape` attribute spelling is deprecated (still works; removal targeted for 3.0). |
125
+ | `closeOnOutsideClick` | `close-on-outside-click` | `boolean` | `true` | Close on a pointer-down outside the trigger and panel (attribute `close-on-outside-click`, default true; switch off with `close-on-outside-click="false"` or the property). Also lets another popover's opening dismiss this one. The `closeonoutsideclick` attribute spelling is deprecated (still works; removal targeted for 3.0). |
126
+ | `arrow` | `arrow` | `boolean` | `false` | Opt-in pointer (audit 23a): a 14×11 triangle on the panel edge that faces the trigger — the Figma Popover "Chevron" — following `side` and tracking the trigger's centre along the cross axis (clamped inside the panel). It is drawn with the panel's tokens (`--rel-popover-arrow-bg` / `--rel-popover-arrow-border`, defaulting to rel-popover-content's background and border). With the arrow on, the panel keeps at least the arrow's height plus 2px away from the trigger so the tip never overlaps it; larger `sideOffset` values still apply. Default off — existing popovers render exactly as before. Reflected. |
127
+ | `hasPopup` | `has-popup` | `"dialog" \| "menu" \| "listbox" \| "tree" \| "grid" \| "true"` | `"dialog"` | New in 1.5.0. Value written to the trigger's `aria-haspopup` (attribute `has-popup`): `dialog` (default) for a panel of content, or `menu`, `listbox`, `tree`, `grid` or `true` when the panel holds that widget. A panel holding a `rel-menu` takes `menu`. |
128
+
129
+ ### Events
130
+
131
+ | Event | Detail | Bubbles / composed | Description |
132
+ | --- | --- | --- | --- |
133
+ | `rel-open-change` | `{ open: next }` | bubbles / composed | Fired whenever the trigger, Escape, an outside click or another popover requests a state change. Detail: `{ open: boolean }` — the requested state; in controlled mode apply it to `open` yourself. |
134
+
135
+ ### Slots
136
+
137
+ | Slot | Description |
138
+ | --- | --- |
139
+ | (default) | The trigger: the first slotted element toggles the panel on pointer down / Enter / Space and receives `aria-haspopup` (from `has-popup`), `aria-expanded` and `aria-controls` (a bare `<div>` / `<span>` also gets `tabindex="0"` and `role="button"`). |
140
+ | `content` | The panel body, usually a `<rel-popover-content>`; rendered inside the fixed, top-layer wrapper only while open. |
141
+
142
+ ### CSS Parts
143
+
144
+ | Part | Description |
145
+ | --- | --- |
146
+ | `arrow` | The 14×14 chevron box rendered on the panel edge facing the trigger when `arrow` is set (absent otherwise). |
147
+
148
+ ### Tokens / CSS Variables
149
+
150
+ #### Component hooks
151
+
152
+ Set these on the host (or an ancestor) to customise the element without touching design tokens.
153
+
154
+ | Custom property | Default | Description |
155
+ | --- | --- | --- |
156
+ | `--rel-popover-arrow-bg` | `var(--rel-bg-neutral)` | Fill of the `arrow` chevron (default `--rel-bg-neutral`, matching `<rel-popover-content>`). |
157
+ | `--rel-popover-arrow-border` | `transparent` | **Deprecated.** Stroke of the `arrow` chevron's two slanted edges. The panel no longer draws a border — overlapping surfaces are separated by shadow — so this defaults to `transparent` and the chevron has no edge. Set it to restore a stroke. |
158
+
159
+ ### `<rel-popover-content>`
160
+
161
+ #### Attributes / Properties
162
+
163
+ | Property | Attribute | Type | Default | Description |
164
+ | --- | --- | --- | --- | --- |
165
+ | `ariaLabel` | `aria-label` | `string` | `""` | Accessible name applied to the inner `role="dialog"` box (attribute `aria-label`); describe what the panel is for. |
166
+ | `size` | `size` | `"small" \| "medium" \| "large" \| "sm" \| "md" \| "lg"` | `"medium"` | Figma Size axis: panel padding 12px (`small`), 16px (`medium`, default) or 20px (`large`). `sm` / `md` / `lg` are alternative spellings and stay supported — they are normalised to the full words, not deprecated. Reflected. |
167
+
168
+ #### Methods
169
+
170
+ | Method | Signature | Description |
171
+ | --- | --- | --- |
172
+ | `focus` | `focus(options?: FocusOptions)` | Focuses the `role="dialog"` box rather than the host (which is not focusable). rel-popover lands focus here when the panel has nothing focusable, so a screen reader announces the dialog by its `aria-label`. |
173
+
174
+ ### Events
175
+
176
+ | Event | Detail | Bubbles / composed | Description |
177
+ | --- | --- | --- | --- |
178
+ | — | — | — | This element emits no custom events. |
179
+
180
+ ### Slots
181
+
182
+ | Slot | Description |
183
+ | --- | --- |
184
+ | (default) | The panel content (headings, paragraphs, form controls, buttons). |
185
+
186
+ ### CSS Parts
187
+
188
+ | Part | Description |
189
+ | --- | --- |
190
+ | `content` | The padded `role="dialog"` box that carries the surface, shadow and size padding. |
191
+
192
+ <!-- readme-gen:end -->
193
+
194
+ ## Accessibility
195
+
196
+ - The trigger (first slotted element) is decorated with `aria-haspopup="dialog"`, `aria-expanded` (kept in sync with the open state) and `aria-controls` pointing at the panel wrapper. A trigger that is not natively focusable and contains nothing focusable (a bare `<div>` / `<span>`) also gets `tabindex="0"` and `role="button"`; native buttons and `<rel-button>` are left alone so they do not get a second Tab stop.
197
+ - `<rel-popover-content>` renders its box with `role="dialog"`; pass `aria-label` so the dialog has a name. A custom panel element should carry its own role and label.
198
+ - On open, focus moves to the first focusable element inside the panel — found through slots and nested shadow roots, so a `<rel-button>` inside `<rel-popover-content>` counts. When there is none, the panel itself takes focus so it is announced: `<rel-popover-content>` focuses its `role="dialog"` box (named by its `aria-label`), and any other panel element falls back to the panel wrapper. On close, focus that was inside the popover returns to the trigger's own control (a `<rel-button>`'s inner `<button>`); a close while focus is elsewhere on the page leaves it there.
199
+ - With `trap-focus`, Tab from the last focusable element wraps to the first and Shift+Tab from the first wraps to the last, so keyboard focus cannot leave the panel while it is open. Without it, Tab leaves the panel normally and the panel stays open until an outside click or Escape.
200
+ - There are no screen-reader announcements beyond the `aria-expanded` change; put the important content first in the panel so it is read when focus lands.
201
+
202
+ | Key | Action |
203
+ | --- | --- |
204
+ | `Enter` / `Space` (on the trigger) | Toggle the panel |
205
+ | `Escape` (anywhere, while open) | Close (unless `closeOnEscape` is switched off) |
206
+ | `Tab` / `Shift+Tab` | Move through the panel's focusable elements; with `trap-focus` the order wraps at both ends |
207
+
208
+ ## Notes / Edge Cases
209
+
210
+ - **Slots.** The trigger is the *default* slot's first element; the panel goes in `slot="content"`. There is no `trigger` slot. Only one element should be in the default slot — extra default-slot children render next to the trigger but never toggle the panel.
211
+ - **Top layer / inside a modal.** The panel wrapper is a native `popover="manual"` element shown with `showPopover()`, so a transformed or `overflow: hidden` ancestor (for example `<rel-modal>`'s centred panel) cannot clip it or break its `position: fixed` coordinates. Where the Popover API is unavailable it falls back to `position: fixed; z-index: 1001` and re-measures once to cancel any containing-block offset.
212
+ - **No collision flipping.** The panel always opens on the requested `side`; it is only clamped to stay 8px inside the viewport. Pick the side yourself for edge-of-screen triggers.
213
+ - **One open at a time.** Opening a popover light-dismisses every other open popover that honours `closeOnOutsideClick`, whether it was opened by pointer, keyboard or code. A popover nested inside another's panel keeps its parent open.
214
+ - **Controlled vs uncontrolled.** `open` is `undefined` until you set it; setting it (to `true` *or* `false`) switches the instance to controlled mode permanently, after which the component only emits `rel-open-change` and never changes its own state. Use `default-open` for an uncontrolled popover that starts open.
215
+ - **Default-true booleans** (`closeOnEscape`, `closeOnOutsideClick`): since 1.5.0 the attribute value `"false"` turns them off (`close-on-escape="false"`), as does setting the property. A bare attribute or `=""` still means true.
216
+ - **Positioning updates** run on window resize and on scroll (capture phase). They do not run when the panel's own content changes size; toggle `open` or trigger a resize if you swap content while open.
217
+ - **Arrow.** `arrow` draws a 14×11 chevron on the panel edge facing the trigger, centred on the trigger and clamped 15px in from the panel corners; the trigger gap becomes `max(side-offset, 13px)`. Colour it with `--rel-popover-arrow-bg` / `--rel-popover-arrow-border` (defaults match `<rel-popover-content>`); if you use a custom panel with a different surface, set both so the chevron blends in.
218
+ - **`<rel-popover-content>`** is `display: block; width: 100%; max-width: 400px`; override `max-width` on the element for wider panels. Slotted `h1`–`h6` get heading-sm type and `p` gets placeholder-tone body text with a bottom margin (none on the last paragraph).
219
+ - **`disabled`** blocks the trigger and dims the host but does not close a panel that is already open — set `open = false` first if needed.
220
+ - The `size` values on `<rel-popover-content>` are `small` / `medium` / `large`. The short spellings `sm` / `md` / `lg` are alternative spellings and keep working — they are normalised to the full words before the attribute reflects, so `rel-popover-content[size="medium"]` matches whichever spelling was written.