@dryui/ui 1.1.3 → 1.1.5

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.
@@ -0,0 +1,42 @@
1
+ <script lang="ts">
2
+ import {
3
+ BorderBeam as BorderBeamPrimitive,
4
+ type BorderBeamProps
5
+ } from '@dryui/primitives/border-beam';
6
+
7
+ let {
8
+ size,
9
+ colorVariant,
10
+ theme,
11
+ staticColors,
12
+ duration,
13
+ active,
14
+ borderRadius,
15
+ brightness,
16
+ saturation,
17
+ hueRange,
18
+ strength,
19
+ onActivate,
20
+ onDeactivate,
21
+ children,
22
+ ...rest
23
+ }: BorderBeamProps = $props();
24
+ </script>
25
+
26
+ <BorderBeamPrimitive
27
+ {size}
28
+ {colorVariant}
29
+ {theme}
30
+ {staticColors}
31
+ {duration}
32
+ {active}
33
+ {borderRadius}
34
+ {brightness}
35
+ {saturation}
36
+ {hueRange}
37
+ {strength}
38
+ {onActivate}
39
+ {onDeactivate}
40
+ {children}
41
+ {...rest}
42
+ />
@@ -0,0 +1,4 @@
1
+ import { type BorderBeamProps } from '@dryui/primitives/border-beam';
2
+ declare const BorderBeam: import("svelte").Component<BorderBeamProps, {}, "">;
3
+ type BorderBeam = ReturnType<typeof BorderBeam>;
4
+ export default BorderBeam;
@@ -0,0 +1,3 @@
1
+ import type { BorderBeamProps, BorderBeamColorVariant, BorderBeamSize, BorderBeamTheme } from '@dryui/primitives/border-beam';
2
+ export type { BorderBeamProps, BorderBeamColorVariant, BorderBeamSize, BorderBeamTheme };
3
+ export { default as BorderBeam } from './border-beam.svelte';
@@ -0,0 +1 @@
1
+ export { default as BorderBeam } from './border-beam.svelte';
@@ -410,7 +410,8 @@
410
410
  --_dry-btn-color: var(--dry-btn-color, var(--dry-color-text-strong));
411
411
  }
412
412
 
413
- &[aria-pressed='true'] {
413
+ &[aria-pressed='true'],
414
+ &[aria-pressed='true']:hover:not([data-disabled]) {
414
415
  --_dry-btn-bg: var(--dry-btn-bg, var(--dry-color-fill-brand));
415
416
  --_dry-btn-color: var(--dry-btn-color, var(--dry-color-on-brand));
416
417
  }
@@ -17,7 +17,7 @@
17
17
  [data-dialog-body] {
18
18
  --dry-section-padding: var(--dry-dialog-padding);
19
19
  padding: var(--dry-section-padding);
20
- overflow-y: auto;
20
+ overflow-y: var(--dry-dialog-body-overflow-y, auto);
21
21
  min-height: 0;
22
22
  }
23
23
  </style>
@@ -46,6 +46,10 @@
46
46
  [data-dialog-content] {
47
47
  position: fixed;
48
48
  inset: 0;
49
+ /* dryui-allow width */
50
+ width: 100vw;
51
+ /* dryui-allow width */
52
+ max-width: none;
49
53
  height: 100vh;
50
54
  height: 100dvh;
51
55
  max-height: none;
@@ -53,9 +57,9 @@
53
57
  background: transparent;
54
58
  color: var(--dry-color-text-strong);
55
59
  padding: 0;
56
- margin: 0;
57
60
  display: grid;
58
61
  grid-template-columns: min(90vw, var(--dry-dialog-max-width, 32rem));
62
+ place-content: center;
59
63
  place-items: center;
60
64
  overflow: visible;
61
65
  }
@@ -89,9 +93,9 @@
89
93
  color: var(--dry-color-text-strong);
90
94
  box-shadow: var(--dry-dialog-shadow, var(--dry-overlay-shadow, var(--dry-shadow-overlay)));
91
95
  padding: 0;
92
- max-height: 85vh;
96
+ max-block-size: var(--dry-dialog-max-block-size, 85vh);
93
97
  display: grid;
94
- overflow: auto;
98
+ overflow: var(--dry-dialog-overflow, auto);
95
99
 
96
100
  transition:
97
101
  opacity var(--dry-duration-normal) var(--dry-ease-spring-snappy),
@@ -16,6 +16,7 @@
16
16
 
17
17
  <style>
18
18
  [data-drawer-body] {
19
+ grid-row: 2;
19
20
  padding: var(--dry-space-12) var(--dry-space-8);
20
21
  overflow-y: auto;
21
22
  min-height: 0;
@@ -14,10 +14,15 @@
14
14
  let dialogEl = $state<HTMLDialogElement>();
15
15
 
16
16
  $effect(() => {
17
- if (ctx.open && dialogEl && !dialogEl.open) {
17
+ if (!dialogEl) return;
18
+
19
+ // Native <dialog> applies a max-width that leaves a strip beside edge drawers.
20
+ dialogEl.style.setProperty('max-width', 'none');
21
+
22
+ if (ctx.open && !dialogEl.open) {
18
23
  dialogEl.showModal();
19
24
  }
20
- if (!ctx.open && dialogEl?.open) {
25
+ if (!ctx.open && dialogEl.open) {
21
26
  dialogEl.close();
22
27
  }
23
28
  });
@@ -55,7 +60,6 @@
55
60
  background: transparent;
56
61
  color: var(--dry-color-text-strong);
57
62
  padding: 0;
58
- margin: 0;
59
63
  box-sizing: border-box;
60
64
  display: grid;
61
65
  grid-template-columns: minmax(0, 1fr);
@@ -102,6 +106,8 @@
102
106
  --dry-drawer-bg: var(--dry-color-bg-overlay);
103
107
  --dry-drawer-border: var(--dry-color-stroke-weak);
104
108
  --dry-drawer-size: 25rem;
109
+ --_drawer-rest-transform: translateX(0);
110
+ --_drawer-enter-transform: translateX(100%);
105
111
 
106
112
  background: var(--dry-drawer-bg);
107
113
  color: var(--dry-color-text-strong);
@@ -110,6 +116,9 @@
110
116
  display: grid;
111
117
  grid-template-rows: max-content minmax(0, 1fr) max-content;
112
118
  overflow: hidden;
119
+ opacity: 1;
120
+ transform: var(--_drawer-rest-transform);
121
+ will-change: transform, opacity;
113
122
 
114
123
  transition:
115
124
  transform var(--dry-duration-slow) var(--dry-ease-spring-snappy),
@@ -123,44 +132,37 @@
123
132
  }
124
133
 
125
134
  [data-drawer-content][data-side='left'] [data-drawer-panel] {
135
+ --_drawer-enter-transform: translateX(-100%);
126
136
  grid-column: 1;
127
137
  height: 100%;
128
138
  border-right: 1px solid var(--dry-drawer-border);
129
139
  }
130
140
 
131
141
  [data-drawer-content][data-side='top'] [data-drawer-panel] {
142
+ --_drawer-rest-transform: translateY(0);
143
+ --_drawer-enter-transform: translateY(-100%);
132
144
  grid-row: 1;
133
145
  height: var(--dry-drawer-size);
134
146
  border-bottom: 1px solid var(--dry-drawer-border);
135
147
  }
136
148
 
137
149
  [data-drawer-content][data-side='bottom'] [data-drawer-panel] {
150
+ --_drawer-rest-transform: translateY(0);
151
+ --_drawer-enter-transform: translateY(100%);
138
152
  grid-row: 2;
139
153
  height: var(--dry-drawer-size);
140
154
  border-top: 1px solid var(--dry-drawer-border);
141
155
  }
142
156
 
143
157
  @starting-style {
144
- [data-drawer-content][data-side='right'][open] [data-drawer-panel] {
145
- opacity: 0;
146
- transform: translateX(100%);
147
- }
148
- [data-drawer-content][data-side='left'][open] [data-drawer-panel] {
149
- opacity: 0;
150
- transform: translateX(-100%);
151
- }
152
- [data-drawer-content][data-side='top'][open] [data-drawer-panel] {
153
- opacity: 0;
154
- transform: translateY(-100%);
155
- }
156
- [data-drawer-content][data-side='bottom'][open] [data-drawer-panel] {
158
+ [data-drawer-content][open] [data-drawer-panel] {
157
159
  opacity: 0;
158
- transform: translateY(100%);
160
+ transform: var(--_drawer-enter-transform);
159
161
  }
160
162
  }
161
163
 
162
164
  [data-drawer-content][data-state='open'] [data-drawer-panel] {
163
165
  opacity: 1;
164
- transform: translate(0);
166
+ transform: var(--_drawer-rest-transform);
165
167
  }
166
168
  </style>
@@ -15,6 +15,7 @@
15
15
 
16
16
  <style>
17
17
  [data-drawer-footer] {
18
+ grid-row: 3;
18
19
  padding: var(--dry-space-6) var(--dry-space-8);
19
20
  border-top: 1px solid var(--dry-drawer-border);
20
21
  display: grid;
@@ -18,6 +18,7 @@
18
18
 
19
19
  <style>
20
20
  [data-drawer-header] {
21
+ grid-row: 1;
21
22
  padding: var(--dry-space-6) var(--dry-space-8);
22
23
  border-bottom: 1px solid var(--dry-drawer-border);
23
24
  }
package/dist/index.d.ts CHANGED
@@ -47,7 +47,7 @@ export type { MultiSelectComboboxRootProps, MultiSelectComboboxInputProps, Multi
47
47
  export { SegmentedControl } from './segmented-control/index.js';
48
48
  export type { SegmentedControlRootProps, SegmentedControlItemProps } from './segmented-control/index.js';
49
49
  export { Slider } from './slider/index.js';
50
- export type { SliderProps } from './slider/index.js';
50
+ export type { SliderProps, SliderVariant } from './slider/index.js';
51
51
  export { Toggle } from './toggle/index.js';
52
52
  export type { ToggleProps } from './toggle/index.js';
53
53
  export { ToggleGroup } from './toggle-group/index.js';
@@ -270,6 +270,8 @@ export { AlphaSlider } from './alpha-slider/index.js';
270
270
  export type { AlphaSliderProps } from './alpha-slider/index.js';
271
271
  export { Beam } from './beam/index.js';
272
272
  export type { BeamProps } from './beam/index.js';
273
+ export { BorderBeam } from './border-beam/index.js';
274
+ export type { BorderBeamProps } from './border-beam/index.js';
273
275
  export { Shimmer } from './shimmer/index.js';
274
276
  export type { ShimmerProps } from './shimmer/index.js';
275
277
  export { Glass } from './glass/index.js';
package/dist/index.js CHANGED
@@ -139,6 +139,7 @@ export { Glow } from './glow/index.js';
139
139
  export { Adjust } from './adjust/index.js';
140
140
  export { AlphaSlider } from './alpha-slider/index.js';
141
141
  export { Beam } from './beam/index.js';
142
+ export { BorderBeam } from './border-beam/index.js';
142
143
  export { Shimmer } from './shimmer/index.js';
143
144
  export { Glass } from './glass/index.js';
144
145
  export { GodRays } from './god-rays/index.js';
@@ -1,5 +1,12 @@
1
+ import type { Snippet } from 'svelte';
1
2
  import type { SliderProps as PrimitiveSliderProps } from '@dryui/primitives';
3
+ export type SliderVariant = 'default' | 'pill';
2
4
  export interface SliderProps extends Omit<PrimitiveSliderProps, 'size'> {
3
5
  size?: 'sm' | 'md' | 'lg';
6
+ variant?: SliderVariant;
7
+ valueLabel?: Snippet<[{
8
+ value: number;
9
+ percentage: number;
10
+ }]>;
4
11
  }
5
12
  export { default as Slider } from './slider-input.svelte';
@@ -1,4 +1,5 @@
1
1
  <script lang="ts">
2
+ import type { Snippet } from 'svelte';
2
3
  import type { HTMLInputAttributes } from 'svelte/elements';
3
4
  import { getFormControlCtx } from '@dryui/primitives';
4
5
 
@@ -8,8 +9,10 @@
8
9
  max?: number;
9
10
  step?: number;
10
11
  size?: 'sm' | 'md' | 'lg';
12
+ variant?: 'default' | 'pill';
11
13
  disabled?: boolean;
12
14
  orientation?: 'horizontal' | 'vertical';
15
+ valueLabel?: Snippet<[{ value: number; percentage: number }]>;
13
16
  }
14
17
 
15
18
  let {
@@ -18,8 +21,10 @@
18
21
  max = 100,
19
22
  step = 1,
20
23
  size = 'md',
24
+ variant = 'default',
21
25
  disabled = false,
22
26
  orientation = 'horizontal',
27
+ valueLabel,
23
28
  class: className,
24
29
  ...rest
25
30
  }: Props = $props();
@@ -28,6 +33,7 @@
28
33
  const isDisabled = $derived(disabled || ctx?.disabled || false);
29
34
 
30
35
  let progress = $derived(((value - min) / (max - min)) * 100);
36
+ let isPill = $derived(variant === 'pill');
31
37
 
32
38
  function applyStyles(node: HTMLElement) {
33
39
  $effect(() => {
@@ -36,7 +42,7 @@
36
42
  }
37
43
  </script>
38
44
 
39
- <span class="wrapper">
45
+ <span class="wrapper" data-variant={variant} data-size={size} use:applyStyles>
40
46
  <input
41
47
  type="range"
42
48
  bind:value
@@ -51,11 +57,20 @@
51
57
  aria-errormessage={ctx?.errorMessageId}
52
58
  data-disabled={isDisabled || undefined}
53
59
  data-orientation={orientation}
60
+ data-variant={variant}
54
61
  data-size={size}
55
62
  class={className}
56
- use:applyStyles
57
63
  {...rest}
58
64
  />
65
+ {#if isPill}
66
+ <span data-part="value-label" aria-hidden="true">
67
+ {#if valueLabel}
68
+ {@render valueLabel({ value, percentage: progress })}
69
+ {:else}
70
+ {Math.round(progress)}%
71
+ {/if}
72
+ </span>
73
+ {/if}
59
74
  </span>
60
75
 
61
76
  <style>
@@ -181,4 +196,81 @@
181
196
  --dry-slider-track-height: 8px;
182
197
  --dry-slider-thumb-size: 28px;
183
198
  }
199
+
200
+ /* ── Pill variant ── */
201
+
202
+ .wrapper[data-variant='pill'] input,
203
+ .wrapper[data-variant='pill'] [data-part='value-label'] {
204
+ grid-area: 1 / 1;
205
+ }
206
+
207
+ input[data-variant='pill'][data-size='sm'] {
208
+ --dry-slider-track-height: 32px;
209
+ --dry-slider-thumb-size: 32px;
210
+ }
211
+
212
+ input[data-variant='pill'][data-size='md'] {
213
+ --dry-slider-track-height: 40px;
214
+ --dry-slider-thumb-size: 40px;
215
+ }
216
+
217
+ input[data-variant='pill'][data-size='lg'] {
218
+ --dry-slider-track-height: 48px;
219
+ --dry-slider-thumb-size: 48px;
220
+ }
221
+
222
+ input[data-variant='pill']::-webkit-slider-thumb {
223
+ background: transparent;
224
+ border: none;
225
+ box-shadow: none;
226
+ margin-top: 0;
227
+ }
228
+
229
+ input[data-variant='pill']::-moz-range-thumb {
230
+ background: transparent;
231
+ border: none;
232
+ box-shadow: none;
233
+ }
234
+
235
+ input[data-variant='pill']:hover:not([data-disabled])::-webkit-slider-thumb,
236
+ input[data-variant='pill']:active:not([data-disabled])::-webkit-slider-thumb {
237
+ transform: none;
238
+ }
239
+
240
+ input[data-variant='pill']:hover:not([data-disabled])::-moz-range-thumb,
241
+ input[data-variant='pill']:active:not([data-disabled])::-moz-range-thumb {
242
+ transform: none;
243
+ }
244
+
245
+ /* Pill value label */
246
+ .wrapper[data-variant='pill'] [data-part='value-label'] {
247
+ pointer-events: none;
248
+ display: grid;
249
+ align-items: center;
250
+ padding-inline: var(--dry-space-4);
251
+ font-size: var(--dry-text-sm-size, 0.875rem);
252
+ font-weight: 600;
253
+ color: var(--dry-color-text);
254
+ clip-path: inset(
255
+ 0 calc(100% - var(--dry-slider-progress, 50%)) 0 0 round var(--dry-radius-full)
256
+ );
257
+ transition: clip-path var(--dry-duration-fast) var(--dry-ease-default);
258
+ z-index: 1;
259
+ }
260
+
261
+ .wrapper[data-variant='pill'][data-size='sm'] [data-part='value-label'] {
262
+ font-size: var(--dry-text-xs-size, 0.75rem);
263
+ padding-inline: var(--dry-space-3);
264
+ }
265
+
266
+ .wrapper[data-variant='pill'][data-size='lg'] [data-part='value-label'] {
267
+ font-size: var(--dry-text-base-size, 1rem);
268
+ padding-inline: var(--dry-space-5);
269
+ }
270
+
271
+ @media (prefers-reduced-motion: reduce) {
272
+ .wrapper[data-variant='pill'] [data-part='value-label'] {
273
+ transition: none;
274
+ }
275
+ }
184
276
  </style>
@@ -1,3 +1,4 @@
1
+ import type { Snippet } from 'svelte';
1
2
  import type { HTMLInputAttributes } from 'svelte/elements';
2
3
  interface Props extends Omit<HTMLInputAttributes, 'size'> {
3
4
  value?: number;
@@ -5,8 +6,13 @@ interface Props extends Omit<HTMLInputAttributes, 'size'> {
5
6
  max?: number;
6
7
  step?: number;
7
8
  size?: 'sm' | 'md' | 'lg';
9
+ variant?: 'default' | 'pill';
8
10
  disabled?: boolean;
9
11
  orientation?: 'horizontal' | 'vertical';
12
+ valueLabel?: Snippet<[{
13
+ value: number;
14
+ percentage: number;
15
+ }]>;
10
16
  }
11
17
  declare const SliderInput: import("svelte").Component<Props, {}, "value">;
12
18
  type SliderInput = ReturnType<typeof SliderInput>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dryui/ui",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "author": "Rob Balfre",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -76,6 +76,11 @@
76
76
  "svelte": "./dist/beam/index.js",
77
77
  "default": "./dist/beam/index.js"
78
78
  },
79
+ "./border-beam": {
80
+ "types": "./dist/border-beam/index.d.ts",
81
+ "svelte": "./dist/border-beam/index.js",
82
+ "default": "./dist/border-beam/index.js"
83
+ },
79
84
  "./breadcrumb": {
80
85
  "types": "./dist/breadcrumb/index.d.ts",
81
86
  "svelte": "./dist/breadcrumb/index.js",
@@ -770,7 +775,7 @@
770
775
  "postpack": "bun ../../scripts/postpack-exports.ts"
771
776
  },
772
777
  "dependencies": {
773
- "@dryui/primitives": "^1.1.3"
778
+ "@dryui/primitives": "^1.1.5"
774
779
  },
775
780
  "peerDependencies": {
776
781
  "svelte": "^5.55.1"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: dryui
3
- description: 'Use when building UIs with DryUI (@dryui/ui) Svelte 5 components. Teaches correct patterns for compound components, theming, forms, and accessibility. Use MCP tools when available; fall back to CLI.'
3
+ description: 'Use when building UIs with DryUI (@dryui/ui) Svelte 5 components. Teaches correct patterns for compound components, theming, forms, and accessibility. Use the CLI as the default entry point; MCP mirrors the same workflow when available.'
4
4
  ---
5
5
 
6
6
  # DryUI
@@ -13,18 +13,18 @@ Zero-dependency Svelte 5 components. All imports from `@dryui/ui`. Requires a th
13
13
 
14
14
  **Never guess a component API. Always verify first.**
15
15
 
16
- - Call `ask --scope component` or `ask --scope recipe` before using any component for the first time
16
+ - Call `dryui info <component>` or `dryui compose "<query>"` before using any component for the first time. If MCP is available, `ask --scope component` and `ask --scope recipe` are equivalent.
17
17
  - Component APIs vary — `bind:value`, `bind:open`, `bind:checked` are NOT interchangeable
18
18
  - Compound vs simple, required parts, available props — all differ per component
19
19
  - If you skip the lookup, you'll write plausible-looking code that silently breaks
20
20
 
21
- The test: can you point to an `ask` call for every component or pattern in your output?
21
+ The test: can you point to a `dryui info`, `dryui compose`, or `ask` call for every component or pattern in your output?
22
22
 
23
23
  ## 2. Everything is Compound Until Proven Otherwise
24
24
 
25
25
  **Use `.Root`. Always check.**
26
26
 
27
- Most DryUI components are compound — they require `<Card.Root>`, not `<Card>`. The bare name silently fails or renders wrong. Assume compound; verify with `ask --scope component`.
27
+ Most DryUI components are compound — they require `<Card.Root>`, not `<Card>`. The bare name silently fails or renders wrong. Assume compound; verify with `dryui info <Component>` or `ask --scope component`.
28
28
 
29
29
  ```svelte
30
30
  <!-- Wrong -->
@@ -33,7 +33,7 @@ Most DryUI components are compound — they require `<Card.Root>`, not `<Card>`.
33
33
  <Card.Root>content</Card.Root>
34
34
  ```
35
35
 
36
- Compound components are tracked in the manifest at `packages/mcp/src/component-catalog.ts`. Verify with `ask --scope component` before you assume a bare name works, then use `.Root` and wrap the parts inside it.
36
+ Compound components are tracked in the manifest at `packages/mcp/src/component-catalog.ts`. Verify with `dryui info <Component>` or `ask --scope component` before you assume a bare name works, then use `.Root` and wrap the parts inside it.
37
37
 
38
38
  The test: every compound component in your markup uses `.Root`, and its parts are wrapped inside it. See `rules/compound-components.md` for the parts reference.
39
39
 
@@ -118,21 +118,19 @@ The test: search your markup for raw `<input`, `<select>`, `<dialog>`, `<button>
118
118
 
119
119
  ## Quick Start
120
120
 
121
- **1. Install this skill** — you're reading it, so it's already loaded. This is the most important step.
121
+ **1. Install the CLI** so every subsequent command is short and fast:
122
122
 
123
- **2. Add the MCP server** for live API lookup and code validation:
124
-
125
- - Claude Code: `claude plugin marketplace add rob-balfre/dryui && claude plugin install dryui@dryui` (installs skill + MCP in one step)
126
- - Codex: public install today is `$skill-installer install https://github.com/rob-balfre/dryui/tree/main/packages/ui/skills/dryui` then `codex mcp add dryui -- npx -y @dryui/mcp`. If you're working inside the DryUI repo itself, install the repo-local plugin from `/plugins` via `.agents/plugins/marketplace.json`.
127
- - Copilot/Cursor/Windsurf: `npx degit rob-balfre/dryui/packages/ui/skills/dryui .agents/skills/dryui` + add MCP config (see https://dryui.dev/tools)
123
+ ```bash
124
+ bun install -g @dryui/cli@latest # or: npm install -g @dryui/cli@latest
125
+ ```
128
126
 
129
- **3. Install the CLI** so every subsequent command is short and fast:
127
+ **2. Start with bare `dryui`** when you want editor integration and feedback:
130
128
 
131
129
  ```bash
132
- bun install -g @dryui/cli # or: npm install -g @dryui/cli
130
+ dryui
133
131
  ```
134
132
 
135
- **4. Bootstrap the project** `init` detects your project state and applies whatever is missing:
133
+ **3. Bootstrap or inspect the project** with the CLI:
136
134
 
137
135
  ```bash
138
136
  dryui init # existing project
@@ -140,10 +138,16 @@ dryui init my-app # new project — scaffolds SvelteKit + DryUI in one step
140
138
  cd my-app && bun run dev
141
139
  ```
142
140
 
143
- This works for greenfield (empty directory), brownfield (existing non-SvelteKit project), and existing SvelteKit projects. Verify: `dryui detect` should show `project: ready`.
141
+ This works for greenfield (empty directory), brownfield (existing non-SvelteKit project), and existing SvelteKit projects. On existing projects, `dryui install` prints the ordered plan and `dryui detect` verifies that setup is complete.
144
142
 
145
143
  > **No global install?** `bunx @dryui/cli <cmd>` and `npx -y @dryui/cli <cmd>` work anywhere without installing — same commands, just slower (re-fetches on each call).
146
144
 
145
+ **4. Add the agent integration layer manually** if you do not want to use `dryui` / `dryui setup`:
146
+
147
+ - Claude Code: `claude plugin marketplace add rob-balfre/dryui && claude plugin install dryui@dryui` (installs skill + MCP in one step)
148
+ - Codex (0.121.0+): `codex marketplace add rob-balfre/dryui`, then start `codex`, run `/plugins`, and install `DryUI` (installs skill + MCP in one step). Manual fallback: `codex mcp add dryui -- npx -y @dryui/mcp` + copy the skill from `packages/ui/skills/dryui`.
149
+ - Copilot/Cursor/Windsurf: `npx degit rob-balfre/dryui/packages/ui/skills/dryui .agents/skills/dryui` + add MCP config (see https://dryui.dev/tools)
150
+
147
151
  ### Manual setup
148
152
 
149
153
  1. `bun add @dryui/ui`
@@ -181,7 +185,7 @@ This works for greenfield (empty directory), brownfield (existing non-SvelteKit
181
185
 
182
186
  ## Bindable Props — Common Confusion
183
187
 
184
- Always verify with `ask --scope component`, but these are the most common mistakes:
188
+ Always verify with `dryui info <Component>` or `ask --scope component`, but these are the most common mistakes:
185
189
 
186
190
  - `bind:value` (Input, Select, Tabs...) vs `bind:checked` (Checkbox, Switch) vs `bind:pressed` (Toggle) vs `bind:open` (Dialog, Popover, Drawer...)
187
191
  - Select and Combobox support both `bind:value` and `bind:open`
@@ -194,25 +198,18 @@ Use these to look up APIs, discover components, plan setup, and validate code.
194
198
 
195
199
  ### Recommended workflow
196
200
 
197
- 1. `ask --scope recipe "<query>"` or `ask --scope component "<Component>"` before writing so you confirm kind, required parts, bindables, and canonical usage.
201
+ 1. `dryui info <Component>` or `dryui compose "<query>"` before writing so you confirm kind, required parts, bindables, and canonical usage. If MCP is available, `ask --scope component` and `ask --scope recipe` are the equivalent surface.
198
202
  2. Build the route or component with raw CSS grid, `Container` for constrained width, and `@container` for responsive layout.
199
- 3. `check` after implementation to catch composition drift, layout violations, and accessibility regressions.
203
+ 3. `dryui review`, `dryui diagnose`, or `dryui doctor` after implementation to catch composition drift, layout violations, and accessibility regressions. If MCP is available, `check` is the equivalent surface.
200
204
  4. Never guess component shape from memory. DryUI is intentionally strict, and the lookup cost is lower than rework.
201
205
 
202
- ### MCP tools (preferred)
206
+ ### CLI (default entry point)
203
207
 
204
- | Workflow | Tools |
205
- | -------------------- | ----------------------------------------------------------------- |
206
- | Project setup | `ask --scope setup ""` |
207
- | Lookup & composition | `ask --scope component`, `ask --scope recipe`, `ask --scope list` |
208
- | Validation | `check <file.svelte>`, `check <theme.css>` |
209
- | Audit | `check`, `check <directory>` |
210
-
211
- ### CLI fallback
212
-
213
- Install once with `bun install -g @dryui/cli` (or `npm install -g @dryui/cli`), then use the short form below. Every command outputs TOON (token-optimized, agent-friendly) by default. Pass `--text` for human-readable plain text, `--json` where supported, or `--full` to disable truncation.
208
+ Install once with `bun install -g @dryui/cli@latest` (or `npm install -g @dryui/cli@latest`), then use the short form below. Every command outputs TOON (token-optimized, agent-friendly) by default. Pass `--text` for human-readable plain text, `--json` where supported, or `--full` to disable truncation.
214
209
 
215
210
  ```bash
211
+ dryui # default onboarding entry point
212
+ dryui setup # explicit onboarding subcommand
216
213
  dryui init [path] [--pm bun] # Bootstrap SvelteKit + DryUI project
217
214
  dryui info <component> # Look up component API
218
215
  dryui compose "date input" # Composition guidance
@@ -227,6 +224,15 @@ dryui list # List components
227
224
 
228
225
  Without a global install, prefix any command with `bunx @dryui/cli …` or `npx -y @dryui/cli …` — same behaviour, just slower (re-fetches on each call).
229
226
 
227
+ ### MCP tools (same workflow in-editor)
228
+
229
+ | Workflow | Tools |
230
+ | -------------------- | ----------------------------------------------------------------- |
231
+ | Project setup | `ask --scope setup ""` |
232
+ | Lookup & composition | `ask --scope component`, `ask --scope recipe`, `ask --scope list` |
233
+ | Validation | `check <file.svelte>`, `check <theme.css>` |
234
+ | Audit | `check`, `check <directory>` |
235
+
230
236
  Categories: action, input, form, layout, navigation, overlay, display, feedback, interaction, utility
231
237
 
232
238
  ## Rule Files
@@ -398,7 +398,7 @@ Use Field.Error to show validation messages.
398
398
 
399
399
  ## Component Selection Quick Reference
400
400
 
401
- Before using any component, call `ask --scope recipe` or `ask --scope component` to get the correct component and usage snippet. This table is a quick reference — `ask` has the full snippets and anti-patterns.
401
+ Before using any component, call `dryui compose "<query>"` or `dryui info <Component>` to get the correct component and usage snippet. If MCP is available, `ask --scope recipe` and `ask --scope component` are equivalent. This table is a quick reference — the CLI and MCP surfaces both return the fuller snippets and anti-patterns.
402
402
 
403
403
  | UI Need | Use This | NOT This |
404
404
  | ----------------- | -------------------------------------- | ---------------------------- |
@@ -431,7 +431,7 @@ Before using any component, call `ask --scope recipe` or `ask --scope component`
431
431
 
432
432
  ## Composition Recipes
433
433
 
434
- Call `ask --scope recipe` with any recipe name to get a full working snippet.
434
+ Call `dryui compose "<recipe>"` with any recipe name to get a full working snippet. If MCP is available, `ask --scope recipe "<recipe>"` is equivalent.
435
435
 
436
436
  | Recipe | Description | Key Components |
437
437
  | ------------------------- | ------------------------- | -------------------------------------- |
@@ -457,7 +457,7 @@ DryUI is a presentation and accessibility system, not a workflow engine. For dep
457
457
  - Normalize route/session state in script before rendering DryUI inputs.
458
458
  - Reset dependent `Select.Root` values when their parent choice changes; do not rely on stale child state surviving domain changes.
459
459
  - Use raw CSS grid to lay out planner sections, and keep orchestration logic in route-level stores or derived state.
460
- - Run `ask` before introducing a new field shape, then run `check` after the flow is wired.
460
+ - Run `dryui info <Component>` or `dryui compose "<pattern>"` before introducing a new field shape, then run `dryui review` or `dryui doctor` after the flow is wired. If MCP is available, `ask` / `check` are equivalent.
461
461
 
462
462
  ```svelte
463
463
  <script lang="ts">
@@ -18,7 +18,7 @@ Every compound component uses `.Root` as the container. Never use the bare name.
18
18
 
19
19
  ## Parts Reference
20
20
 
21
- Below are the parts for the most commonly used compound components. Always run `ask --scope component "<name>"` or `dryui info <name>` for the full, up-to-date parts list.
21
+ Below are the parts for the most commonly used compound components. Prefer `dryui info <name>` for the full, up-to-date parts list; if MCP is available, `ask --scope component "<name>"` is equivalent.
22
22
 
23
23
  ### Card
24
24
 
@@ -311,6 +311,6 @@ Parts: Root, Input, Content, Item, Empty
311
311
 
312
312
  ## Full Compound Component List
313
313
 
314
- Run `ask --scope component "<name>"` or `dryui info <name>` for any component's complete parts list:
314
+ Run `dryui info <name>` for any component's complete parts list. If MCP is available, `ask --scope component "<name>"` is equivalent:
315
315
 
316
316
  Accordion, AlertDialog, Breadcrumb, Card, Collapsible, ColorPicker, Combobox, CommandPalette, ContextMenu, DataGrid, DatePicker, Dialog, DragAndDrop, Drawer, DropdownMenu, EmptyState, Field, FileUpload, FloatButton, Pagination, Popover, RadioGroup, RichTextEditor, Select, Splitter, Stepper, Table, Tabs, TagsInput, Toast, ToggleGroup, Toolbar, Tooltip, Tour, Transfer
@@ -268,11 +268,11 @@ Ensure sufficient contrast between text and background.
268
268
 
269
269
  ## Validating Theme CSS
270
270
 
271
- Run `check <theme.css>` to catch theme issues. CLI fallback:
271
+ Run `dryui diagnose <theme.css>` to catch theme issues. If MCP is available, `check <theme.css>` is equivalent:
272
272
 
273
273
  ```bash
274
- check src/styles/global.css
275
274
  dryui diagnose src/styles/global.css
275
+ check src/styles/global.css
276
276
  ```
277
277
 
278
278
  Common diagnostic codes:
@@ -1,25 +0,0 @@
1
- <script lang="ts">
2
- import type { HTMLAttributes } from 'svelte/elements';
3
-
4
- interface Props extends HTMLAttributes<HTMLDivElement> {}
5
-
6
- let { class: className, ...rest }: Props = $props();
7
- </script>
8
-
9
- <div class={className} {...rest}></div>
10
-
11
- <style>
12
- div {
13
- position: fixed;
14
- inset: 0;
15
- background: var(--dry-overlay-bg, var(--dry-color-overlay-backdrop));
16
- backdrop-filter: blur(var(--dry-overlay-blur, 12px));
17
- -webkit-backdrop-filter: blur(var(--dry-overlay-blur, 12px));
18
- z-index: var(--dry-layer-overlay);
19
- will-change: opacity;
20
- }
21
-
22
- div[data-state='closed'] {
23
- display: none;
24
- }
25
- </style>
@@ -1,6 +0,0 @@
1
- import type { HTMLAttributes } from 'svelte/elements';
2
- interface Props extends HTMLAttributes<HTMLDivElement> {
3
- }
4
- declare const OverlayBase: import("svelte").Component<Props, {}, "">;
5
- type OverlayBase = ReturnType<typeof OverlayBase>;
6
- export default OverlayBase;
@@ -1,22 +0,0 @@
1
- <script lang="ts">
2
- import type { Snippet } from 'svelte';
3
- import type { HTMLAttributes } from 'svelte/elements';
4
-
5
- interface Props extends HTMLAttributes<HTMLDivElement> {
6
- children: Snippet;
7
- }
8
-
9
- let { children, class: className, ...rest }: Props = $props();
10
- </script>
11
-
12
- <div class={className} {...rest}>
13
- {@render children()}
14
- </div>
15
-
16
- <style>
17
- div {
18
- padding: var(--dry-section-padding);
19
- overflow-y: auto;
20
- min-height: 0;
21
- }
22
- </style>
@@ -1,8 +0,0 @@
1
- import type { Snippet } from 'svelte';
2
- import type { HTMLAttributes } from 'svelte/elements';
3
- interface Props extends HTMLAttributes<HTMLDivElement> {
4
- children: Snippet;
5
- }
6
- declare const SectionBody: import("svelte").Component<Props, {}, "">;
7
- type SectionBody = ReturnType<typeof SectionBody>;
8
- export default SectionBody;
@@ -1,26 +0,0 @@
1
- <script lang="ts">
2
- import type { Snippet } from 'svelte';
3
- import type { HTMLAttributes } from 'svelte/elements';
4
-
5
- interface Props extends HTMLAttributes<HTMLDivElement> {
6
- children: Snippet;
7
- }
8
-
9
- let { children, class: className, ...rest }: Props = $props();
10
- </script>
11
-
12
- <div class={className} {...rest}>
13
- {@render children()}
14
- </div>
15
-
16
- <style>
17
- div {
18
- padding: var(--dry-section-padding);
19
- border-top: 1px solid var(--dry-section-border);
20
- display: grid;
21
- grid-auto-flow: column;
22
- grid-auto-columns: max-content;
23
- justify-items: var(--dry-section-footer-justify, start);
24
- gap: var(--dry-section-footer-gap, var(--dry-space-4));
25
- }
26
- </style>
@@ -1,8 +0,0 @@
1
- import type { Snippet } from 'svelte';
2
- import type { HTMLAttributes } from 'svelte/elements';
3
- interface Props extends HTMLAttributes<HTMLDivElement> {
4
- children: Snippet;
5
- }
6
- declare const SectionFooter: import("svelte").Component<Props, {}, "">;
7
- type SectionFooter = ReturnType<typeof SectionFooter>;
8
- export default SectionFooter;
@@ -1,21 +0,0 @@
1
- <script lang="ts">
2
- import type { Snippet } from 'svelte';
3
- import type { HTMLAttributes } from 'svelte/elements';
4
-
5
- interface Props extends HTMLAttributes<HTMLDivElement> {
6
- children: Snippet;
7
- }
8
-
9
- let { children, class: className, ...rest }: Props = $props();
10
- </script>
11
-
12
- <div class={className} {...rest}>
13
- {@render children()}
14
- </div>
15
-
16
- <style>
17
- div {
18
- padding: var(--dry-section-padding);
19
- border-bottom: 1px solid var(--dry-section-border);
20
- }
21
- </style>
@@ -1,8 +0,0 @@
1
- import type { Snippet } from 'svelte';
2
- import type { HTMLAttributes } from 'svelte/elements';
3
- interface Props extends HTMLAttributes<HTMLDivElement> {
4
- children: Snippet;
5
- }
6
- declare const SectionHeader: import("svelte").Component<Props, {}, "">;
7
- type SectionHeader = ReturnType<typeof SectionHeader>;
8
- export default SectionHeader;
@@ -1,46 +0,0 @@
1
- # Practical UI Foundation Map
2
-
3
- > Living note for [docs/research/practical-ui-figma-extraction.md](./docs/research/practical-ui-figma-extraction.md). Extracted from the Practical UI design system file on 2026-03-25.
4
-
5
- ## Foundation Rules
6
-
7
- - Colour tokens are organized as semantic roles first, then tone, emphasis, and state.
8
- - Background surfaces use `base`, `sunken`, `raised`, and `overlay`.
9
- - Practical UI’s public neutral text and fill system is transparent, not solid.
10
- - Elevation is expressed with both surface colour and shadow.
11
- - Spacing is a fixed 4px-based ladder, not ad hoc values.
12
- - Typography is centered on Inter with a small named scale.
13
-
14
- ## DryUI Mapping
15
-
16
- | Figma | DryUI |
17
- | -------------------- | --------------------------- |
18
- | `Background/Base` | `--dry-color-bg-base` |
19
- | `Background/Sunken` | `--dry-color-bg-sunken` |
20
- | `Background/Raised` | `--dry-color-bg-raised` |
21
- | `Background/Overlay` | `--dry-color-bg-overlay` |
22
- | `Background/Inverse` | `--dry-color-bg-inverse` |
23
- | `Text/Strong` | `--dry-color-text-strong` |
24
- | `Text/Weak` | `--dry-color-text-weak` |
25
- | `Text/Disabled` | `--dry-color-text-disabled` |
26
- | `Stroke/Strong` | `--dry-color-stroke-strong` |
27
- | `Stroke/Weak` | `--dry-color-stroke-weak` |
28
- | `Stroke/Focus` | `--dry-color-stroke-focus` |
29
- | `Fill/Strong` | `--dry-color-fill-strong` |
30
- | `Fill/Weak` | `--dry-color-fill-weak` |
31
- | `Fill/Hover` | `--dry-color-fill-hover` |
32
- | `Fill/Press` | `--dry-color-fill-active` |
33
- | `Fill/Selected` | `--dry-color-fill-selected` |
34
- | `Fill/Inverse` | `--dry-color-fill-inverse` |
35
- | `Information` | `info` |
36
-
37
- ## Published Scales
38
-
39
- - Spacing: `0`, `4`, `8`, `12`, `16`, `20`, `24`, `32`, `40`, `48`, `56`, `64`, `80`, `96`, `128`, `192`, `256`
40
- - Desktop typography: `Display 56/64`, `Heading 1 40/48`, `Heading 2 32/40`, `Heading 3 24/32`, `Heading 4 20/28`, `Small 16/24`, `Tiny 14/20`
41
- - Mobile typography: `Display 40/48`, `Heading 1 36/44`, `Heading 2 28/36`, `Heading 3 24/32`, `Heading 4 20/28`, `Small 16/24`, `Tiny 14/20`
42
-
43
- ## Notes
44
-
45
- - Keep [`default.css`](./packages/ui/src/themes/default.css) and [`dark.css`](./packages/ui/src/themes/dark.css) synchronized with the extracted foundation ladder.
46
- - The inverse fill family in Dark mode follows the same transparent-neutral logic as the rest of the semantic layer.