@dryui/ui 2.0.2 → 3.0.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.
- package/dist/accordion/accordion-button-trigger.svelte +1 -0
- package/dist/button/button.svelte +2 -1
- package/dist/color-picker/color-picker-channel-input.svelte +0 -1
- package/dist/combobox/combobox-content.svelte +0 -1
- package/dist/file-select/file-select-root.svelte +75 -15
- package/dist/file-select/file-select-root.svelte.d.ts +2 -0
- package/dist/file-upload/file-upload-item.svelte +0 -1
- package/dist/internal/modal-content.svelte +4 -6
- package/dist/internal/picker-popover-content.svelte +0 -1
- package/dist/kbd/kbd.svelte +1 -1
- package/dist/mega-menu/mega-menu-panel.svelte +1 -1
- package/dist/menubar/menubar-content.svelte +0 -1
- package/dist/menubar/menubar-root.svelte +0 -1
- package/dist/multi-select-combobox/multi-select-combobox-content.svelte +0 -1
- package/dist/multi-select-combobox/multi-select-combobox-root-input.svelte +7 -3
- package/dist/navigation-menu/navigation-menu-link.svelte +0 -1
- package/dist/option-picker/option-picker-preview.svelte +1 -0
- package/dist/rich-text-editor/rich-text-editor-toolbar-button-input.svelte +0 -1
- package/dist/tags-input/tags-input-root.svelte +0 -1
- package/dist/toast/toast-root.svelte +0 -1
- package/dist/toolbar/toolbar-root.svelte +0 -1
- package/dist/tree/tree-item-label.svelte +0 -2
- package/dist/tree/tree-root.svelte +0 -1
- package/package.json +5 -5
- package/skills/dryui/SKILL.md +71 -73
- package/skills/dryui/rules/composition.md +3 -3
- package/skills/dryui/rules/compound-components.md +2 -2
- package/skills/dryui/rules/native-web-transitions.md +1 -1
- package/skills/dryui/rules/design-brief.md +0 -59
- package/skills/dryui/rules/design.md +0 -71
|
@@ -179,6 +179,7 @@
|
|
|
179
179
|
--_dry-btn-color: var(--dry-btn-color, var(--_dry-btn-on-accent));
|
|
180
180
|
--_dry-btn-border: var(--dry-btn-border, transparent);
|
|
181
181
|
--_dry-btn-radius: var(--dry-btn-radius, var(--dry-radius-md));
|
|
182
|
+
--_dry-btn-active-transform: var(--dry-btn-active-transform, scale(0.98));
|
|
182
183
|
--_dry-btn-padding-x: var(--dry-btn-padding-x, var(--dry-space-4));
|
|
183
184
|
--_dry-btn-padding-y: var(--dry-btn-padding-y, var(--dry-space-2_5));
|
|
184
185
|
--_dry-btn-font-size: var(
|
|
@@ -236,7 +237,7 @@
|
|
|
236
237
|
}
|
|
237
238
|
|
|
238
239
|
&:active:not([data-disabled]) {
|
|
239
|
-
transform:
|
|
240
|
+
transform: var(--_dry-btn-active-transform);
|
|
240
241
|
}
|
|
241
242
|
|
|
242
243
|
&[data-disabled] {
|
|
@@ -59,7 +59,6 @@
|
|
|
59
59
|
font-family: var(--dry-font-mono);
|
|
60
60
|
color: var(--dry-color-text-strong);
|
|
61
61
|
background: var(--dry-color-bg-raised);
|
|
62
|
-
/* dryui-allow solid-border-on-raised: numeric color channel inputs use field affordance inside a raised picker. */
|
|
63
62
|
border: 1px solid var(--dry-color-stroke-strong);
|
|
64
63
|
border-radius: var(--dry-radius-md);
|
|
65
64
|
transition:
|
|
@@ -94,7 +94,6 @@
|
|
|
94
94
|
max-content
|
|
95
95
|
);
|
|
96
96
|
background: var(--dry-color-bg-overlay);
|
|
97
|
-
/* dryui-allow solid-border-on-raised: popover listbox keeps a crisp edge for option scanning. */
|
|
98
97
|
border: 1px solid var(--dry-color-stroke-weak);
|
|
99
98
|
border-radius: var(--dry-radius-md);
|
|
100
99
|
box-shadow: var(--dry-shadow-lg);
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
interface Props {
|
|
6
6
|
value?: string | null;
|
|
7
|
+
accept?: string | undefined;
|
|
8
|
+
directory?: boolean | undefined;
|
|
7
9
|
onrequest?: (() => Promise<string | null>) | undefined;
|
|
8
10
|
onchange?: ((value: string | null) => void) | undefined;
|
|
9
11
|
disabled?: boolean | undefined;
|
|
@@ -12,6 +14,8 @@
|
|
|
12
14
|
|
|
13
15
|
let {
|
|
14
16
|
value = $bindable(null),
|
|
17
|
+
accept = '',
|
|
18
|
+
directory = true,
|
|
15
19
|
onrequest,
|
|
16
20
|
onchange,
|
|
17
21
|
disabled = false,
|
|
@@ -19,20 +23,56 @@
|
|
|
19
23
|
}: Props = $props();
|
|
20
24
|
|
|
21
25
|
let loading = $state(false);
|
|
26
|
+
let fallbackInputEl: HTMLInputElement | null = null;
|
|
27
|
+
|
|
28
|
+
function commitValue(nextValue: string | null) {
|
|
29
|
+
if (nextValue === null) return;
|
|
30
|
+
|
|
31
|
+
value = nextValue;
|
|
32
|
+
onchange?.(nextValue);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getFallbackInputValue(files: FileList | null): string | null {
|
|
36
|
+
const file = files?.[0];
|
|
37
|
+
if (!file) return null;
|
|
38
|
+
|
|
39
|
+
const directoryName = file.webkitRelativePath.split('/').find(Boolean);
|
|
40
|
+
return directory && directoryName ? directoryName : file.name;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function openFallbackInput() {
|
|
44
|
+
if (!fallbackInputEl) return;
|
|
45
|
+
|
|
46
|
+
fallbackInputEl.value = '';
|
|
47
|
+
fallbackInputEl.click();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function attachFallbackInput(node: HTMLInputElement) {
|
|
51
|
+
fallbackInputEl = node;
|
|
52
|
+
return () => {
|
|
53
|
+
if (fallbackInputEl === node) {
|
|
54
|
+
fallbackInputEl = null;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
22
58
|
|
|
23
59
|
async function defaultRequest(): Promise<string | null> {
|
|
24
60
|
if (typeof window === 'undefined') return null;
|
|
25
|
-
if (
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
61
|
+
if (directory && 'showDirectoryPicker' in window) {
|
|
62
|
+
try {
|
|
63
|
+
const handle = await (
|
|
64
|
+
window as unknown as {
|
|
65
|
+
showDirectoryPicker: (opts: { mode: string }) => Promise<{ name: string }>;
|
|
66
|
+
}
|
|
67
|
+
).showDirectoryPicker({ mode: 'read' });
|
|
68
|
+
return handle.name;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
if (error instanceof DOMException && error.name === 'AbortError') return null;
|
|
71
|
+
}
|
|
35
72
|
}
|
|
73
|
+
|
|
74
|
+
openFallbackInput();
|
|
75
|
+
return null;
|
|
36
76
|
}
|
|
37
77
|
|
|
38
78
|
async function request() {
|
|
@@ -41,15 +81,17 @@
|
|
|
41
81
|
try {
|
|
42
82
|
const pick = onrequest ?? defaultRequest;
|
|
43
83
|
const result = await pick();
|
|
44
|
-
|
|
45
|
-
value = result;
|
|
46
|
-
onchange?.(result);
|
|
47
|
-
}
|
|
84
|
+
commitValue(result);
|
|
48
85
|
} finally {
|
|
49
86
|
loading = false;
|
|
50
87
|
}
|
|
51
88
|
}
|
|
52
89
|
|
|
90
|
+
function handleFallbackInputChange(event: Event & { currentTarget: HTMLInputElement }) {
|
|
91
|
+
commitValue(getFallbackInputValue(event.currentTarget.files));
|
|
92
|
+
event.currentTarget.value = '';
|
|
93
|
+
}
|
|
94
|
+
|
|
53
95
|
function clear() {
|
|
54
96
|
if (disabled) return;
|
|
55
97
|
value = null;
|
|
@@ -72,10 +114,29 @@
|
|
|
72
114
|
</script>
|
|
73
115
|
|
|
74
116
|
<div data-file-select data-disabled={disabled || undefined}>
|
|
117
|
+
<input
|
|
118
|
+
{@attach attachFallbackInput}
|
|
119
|
+
type="file"
|
|
120
|
+
{accept}
|
|
121
|
+
webkitdirectory={directory}
|
|
122
|
+
disabled={disabled || undefined}
|
|
123
|
+
aria-hidden="true"
|
|
124
|
+
tabindex="-1"
|
|
125
|
+
class="file-select-hidden-input"
|
|
126
|
+
onchange={handleFallbackInputChange}
|
|
127
|
+
/>
|
|
128
|
+
|
|
75
129
|
{@render children()}
|
|
76
130
|
</div>
|
|
77
131
|
|
|
78
132
|
<style>
|
|
133
|
+
.file-select-hidden-input {
|
|
134
|
+
display: none;
|
|
135
|
+
position: absolute;
|
|
136
|
+
height: 0;
|
|
137
|
+
overflow: hidden;
|
|
138
|
+
}
|
|
139
|
+
|
|
79
140
|
[data-file-select] {
|
|
80
141
|
container-type: inline-size;
|
|
81
142
|
display: grid;
|
|
@@ -85,7 +146,6 @@
|
|
|
85
146
|
padding: var(--dry-space-2) var(--dry-space-3);
|
|
86
147
|
font-family: var(--dry-font-sans);
|
|
87
148
|
background: var(--dry-color-bg-raised);
|
|
88
|
-
/* dryui-allow solid-border-on-raised: file select is a form control and needs a persistent field edge. */
|
|
89
149
|
border: 1px solid var(--dry-color-stroke-strong);
|
|
90
150
|
border-radius: var(--dry-radius-md);
|
|
91
151
|
transition:
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
interface Props {
|
|
3
3
|
value?: string | null;
|
|
4
|
+
accept?: string | undefined;
|
|
5
|
+
directory?: boolean | undefined;
|
|
4
6
|
onrequest?: (() => Promise<string | null>) | undefined;
|
|
5
7
|
onchange?: ((value: string | null) => void) | undefined;
|
|
6
8
|
disabled?: boolean | undefined;
|
|
@@ -27,7 +27,6 @@
|
|
|
27
27
|
font-family: var(--dry-font-sans);
|
|
28
28
|
color: var(--dry-color-text-strong);
|
|
29
29
|
background: var(--dry-color-bg-raised);
|
|
30
|
-
/* dryui-allow solid-border-on-raised: uploaded file rows need a visible item boundary in dense lists. */
|
|
31
30
|
border: 1px solid var(--dry-color-stroke-weak);
|
|
32
31
|
border-radius: var(--dry-radius-md);
|
|
33
32
|
}
|
|
@@ -106,9 +106,9 @@
|
|
|
106
106
|
/* ---------- Dialog (center) ---------- */
|
|
107
107
|
|
|
108
108
|
[data-modal-content][data-variant='dialog'] {
|
|
109
|
-
/* dryui-allow width */
|
|
109
|
+
/* dryui-allow width: resets native <dialog> sizing so the outer grid track owns the centered panel width. */
|
|
110
110
|
width: 100vw;
|
|
111
|
-
/* dryui-allow width */
|
|
111
|
+
/* dryui-allow width: paired with the width reset above. */
|
|
112
112
|
max-width: none;
|
|
113
113
|
display: grid;
|
|
114
114
|
grid-template-columns: min(90vw, var(--dry-dialog-max-width, 32rem));
|
|
@@ -172,9 +172,9 @@
|
|
|
172
172
|
/* ---------- AlertDialog (center) ---------- */
|
|
173
173
|
|
|
174
174
|
[data-modal-content][data-variant='alert-dialog'] {
|
|
175
|
-
/* dryui-allow width */
|
|
175
|
+
/* dryui-allow width: resets native <dialog> sizing so the outer grid track owns the centered panel width. */
|
|
176
176
|
width: 100vw;
|
|
177
|
-
/* dryui-allow width */
|
|
177
|
+
/* dryui-allow width: paired with the width reset above. */
|
|
178
178
|
max-width: none;
|
|
179
179
|
display: grid;
|
|
180
180
|
grid-template-columns: min(90vw, var(--dry-dialog-max-width, 32rem));
|
|
@@ -319,7 +319,6 @@
|
|
|
319
319
|
|
|
320
320
|
[data-modal-content][data-variant='drawer'][data-side='top'] [data-modal-panel] {
|
|
321
321
|
--_drawer-rest-transform: translateY(0);
|
|
322
|
-
/* dryui-allow symmetric-exit-animation: this is the off-canvas enter position for a top drawer, not the exit animation. */
|
|
323
322
|
--_drawer-enter-transform: translateY(-100%);
|
|
324
323
|
grid-row: 1;
|
|
325
324
|
height: var(--dry-drawer-size);
|
|
@@ -328,7 +327,6 @@
|
|
|
328
327
|
|
|
329
328
|
[data-modal-content][data-variant='drawer'][data-side='bottom'] [data-modal-panel] {
|
|
330
329
|
--_drawer-rest-transform: translateY(0);
|
|
331
|
-
/* dryui-allow symmetric-exit-animation: this is the off-canvas enter position for a bottom drawer, not the exit animation. */
|
|
332
330
|
--_drawer-enter-transform: translateY(100%);
|
|
333
331
|
grid-row: 2;
|
|
334
332
|
height: var(--dry-drawer-size);
|
|
@@ -82,7 +82,6 @@
|
|
|
82
82
|
margin: 0;
|
|
83
83
|
display: inline-grid;
|
|
84
84
|
padding: var(--dry-space-3);
|
|
85
|
-
/* dryui-allow solid-border-on-raised: date picker popovers need a clear calendar boundary. */
|
|
86
85
|
border: 1px solid var(--dry-color-stroke-weak);
|
|
87
86
|
border-radius: var(--dry-radius-lg);
|
|
88
87
|
background: var(--dry-color-bg-overlay);
|
package/dist/kbd/kbd.svelte
CHANGED
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
font-size: var(--dry-kbd-font-size);
|
|
46
46
|
font-weight: 600;
|
|
47
47
|
line-height: 1;
|
|
48
|
-
box-shadow: inset 0
|
|
48
|
+
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--dry-color-text-strong) 10%, transparent);
|
|
49
49
|
user-select: none;
|
|
50
50
|
white-space: nowrap;
|
|
51
51
|
}
|
|
@@ -117,7 +117,7 @@
|
|
|
117
117
|
border-radius: var(--dry-radius-lg, 0.5rem);
|
|
118
118
|
box-shadow: var(--dry-shadow-lg, 0 10px 15px -3px rgba(0, 0, 0, 0.1));
|
|
119
119
|
padding: var(--dry-space-4, 1rem);
|
|
120
|
-
/* dryui-allow width */
|
|
120
|
+
/* dryui-allow width: viewport cap for the anchored floating panel, not a child-layout sizing rule. */
|
|
121
121
|
max-inline-size: var(--dry-mega-menu-panel-max-width, min(92vw, 60rem));
|
|
122
122
|
display: grid;
|
|
123
123
|
grid-auto-flow: column;
|
|
@@ -123,7 +123,6 @@
|
|
|
123
123
|
|
|
124
124
|
background: var(--dry-color-bg-overlay);
|
|
125
125
|
color: var(--dry-color-text-strong);
|
|
126
|
-
/* dryui-allow solid-border-on-raised: menu popover keeps a visible edge while floating above varied content. */
|
|
127
126
|
border: 1px solid var(--dry-color-stroke-weak);
|
|
128
127
|
border-radius: var(--dry-radius-popover);
|
|
129
128
|
box-shadow: var(--dry-shadow-overlay);
|
|
@@ -82,7 +82,6 @@
|
|
|
82
82
|
gap: var(--dry-space-1);
|
|
83
83
|
padding: var(--dry-space-1);
|
|
84
84
|
background: var(--dry-color-bg-overlay);
|
|
85
|
-
/* dryui-allow solid-border-on-raised: menubar root presents a persistent command strip boundary. */
|
|
86
85
|
border: 1px solid var(--dry-color-stroke-weak);
|
|
87
86
|
border-radius: var(--dry-radius-lg);
|
|
88
87
|
}
|
|
@@ -87,7 +87,6 @@
|
|
|
87
87
|
margin: 0;
|
|
88
88
|
grid-template-columns: minmax(anchor-size(inline), max-content);
|
|
89
89
|
background: var(--dry-color-bg-overlay);
|
|
90
|
-
/* dryui-allow solid-border-on-raised: popover menu keeps an explicit edge for contrast against busy app surfaces. */
|
|
91
90
|
border: 1px solid var(--dry-color-stroke-weak);
|
|
92
91
|
border-radius: var(--dry-radius-md);
|
|
93
92
|
box-shadow: var(--dry-shadow-lg);
|
|
@@ -82,11 +82,16 @@
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
function selectValue(itemValue: string) {
|
|
85
|
-
if (disabled ||
|
|
85
|
+
if (disabled || !canSelectValue(itemValue)) {
|
|
86
86
|
return false;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
if (value.includes(itemValue)) {
|
|
90
|
+
setSelectedValues(value.filter((entry) => entry !== itemValue));
|
|
91
|
+
} else {
|
|
92
|
+
setSelectedValues([...value, itemValue]);
|
|
93
|
+
}
|
|
94
|
+
|
|
90
95
|
setQueryValue('');
|
|
91
96
|
activeItemId = '';
|
|
92
97
|
focusInput();
|
|
@@ -251,7 +256,6 @@
|
|
|
251
256
|
justify-content: start;
|
|
252
257
|
gap: var(--dry-space-1_5);
|
|
253
258
|
padding: var(--dry-space-2);
|
|
254
|
-
/* dryui-allow solid-border-on-raised: composite input needs a persistent field edge plus raised fill for token contrast. */
|
|
255
259
|
border: 1px solid var(--dry-color-stroke-strong);
|
|
256
260
|
border-radius: var(--dry-radius-md);
|
|
257
261
|
background: var(--dry-color-bg-raised);
|
|
@@ -87,6 +87,7 @@
|
|
|
87
87
|
var(--_preset-color) 58%,
|
|
88
88
|
color-mix(in srgb, black 12%, var(--_preset-color) 88%) 100%
|
|
89
89
|
);
|
|
90
|
+
/* dryui-allow inset-shadow: 1px top highlight + 1px bottom shadow give the preset color swatch a beveled material look. */
|
|
90
91
|
box-shadow:
|
|
91
92
|
inset 0 1px 0 color-mix(in srgb, white 22%, transparent),
|
|
92
93
|
inset 0 -1px 0 color-mix(in srgb, black 10%, transparent);
|
|
@@ -516,7 +516,6 @@
|
|
|
516
516
|
gap: var(--dry-space-2);
|
|
517
517
|
padding: var(--dry-space-2);
|
|
518
518
|
background: var(--dry-color-bg-overlay);
|
|
519
|
-
/* dryui-allow solid-border-on-raised: inline link editor is a small popover that needs a distinct edge. */
|
|
520
519
|
border: 1px solid var(--dry-rte-border);
|
|
521
520
|
border-radius: var(--dry-radius-md);
|
|
522
521
|
box-shadow: var(--dry-shadow-md);
|
|
@@ -86,7 +86,6 @@
|
|
|
86
86
|
[data-part='root'] {
|
|
87
87
|
display: block;
|
|
88
88
|
padding: var(--dry-space-2);
|
|
89
|
-
/* dryui-allow solid-border-on-raised: tag editor is a form field with token chips on a raised input surface. */
|
|
90
89
|
border: 1px solid var(--dry-color-stroke-strong);
|
|
91
90
|
border-radius: var(--dry-radius-md);
|
|
92
91
|
background: var(--dry-color-bg-raised);
|
|
@@ -89,7 +89,6 @@
|
|
|
89
89
|
gap: var(--dry-space-1);
|
|
90
90
|
padding: var(--dry-space-1);
|
|
91
91
|
background: var(--dry-color-bg-overlay);
|
|
92
|
-
/* dryui-allow solid-border-on-raised: toolbar chrome needs a visible control group boundary. */
|
|
93
92
|
border: 1px solid var(--dry-color-stroke-weak);
|
|
94
93
|
border-radius: var(--dry-radius-lg);
|
|
95
94
|
}
|
|
@@ -51,8 +51,6 @@
|
|
|
51
51
|
[data-part='label'][data-selected] {
|
|
52
52
|
background: var(--dry-tree-item-selected-bg, var(--dry-color-fill-brand-weak));
|
|
53
53
|
color: var(--dry-tree-item-selected-color, var(--dry-color-text-brand));
|
|
54
|
-
box-shadow: inset 2px 0 0
|
|
55
|
-
var(--dry-tree-item-selected-indicator, var(--dry-color-stroke-selected));
|
|
56
54
|
font-weight: 600;
|
|
57
55
|
}
|
|
58
56
|
</style>
|
|
@@ -238,7 +238,6 @@
|
|
|
238
238
|
--dry-tree-item-hover-bg: var(--dry-color-fill);
|
|
239
239
|
--dry-tree-item-selected-bg: var(--dry-color-fill-brand-weak);
|
|
240
240
|
--dry-tree-item-selected-color: var(--dry-color-text-brand);
|
|
241
|
-
--dry-tree-item-selected-indicator: var(--dry-color-stroke-selected);
|
|
242
241
|
--dry-tree-icon-size: 1rem;
|
|
243
242
|
}
|
|
244
243
|
</style>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dryui/ui",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Zero-dependency styled Svelte 5 components with scoped styles and --dry-* CSS variable theming.",
|
|
5
5
|
"author": "Rob Balfre",
|
|
6
6
|
"license": "MIT",
|
|
@@ -817,14 +817,14 @@
|
|
|
817
817
|
"check:publish-hygiene": "bun run check:publint && bun run check:attw"
|
|
818
818
|
},
|
|
819
819
|
"dependencies": {
|
|
820
|
-
"@dryui/primitives": "^
|
|
820
|
+
"@dryui/primitives": "^3.0.0"
|
|
821
821
|
},
|
|
822
822
|
"peerDependencies": {
|
|
823
|
-
"svelte": "^5.55.
|
|
823
|
+
"svelte": "^5.55.5"
|
|
824
824
|
},
|
|
825
825
|
"devDependencies": {
|
|
826
|
-
"@dryui/lint": "^0.
|
|
827
|
-
"svelte": "^5.55.
|
|
826
|
+
"@dryui/lint": "^1.0.0",
|
|
827
|
+
"svelte": "^5.55.5",
|
|
828
828
|
"@sveltejs/package": "^2.5.7",
|
|
829
829
|
"svelte-check": "^4.4.6",
|
|
830
830
|
"typescript": "^6.0.3"
|
package/skills/dryui/SKILL.md
CHANGED
|
@@ -11,41 +11,45 @@ Zero-dependency Svelte 5 components. All imports from `@dryui/ui`. Requires a th
|
|
|
11
11
|
|
|
12
12
|
## UI Creation Pipeline
|
|
13
13
|
|
|
14
|
-
DryUI work is explicit.
|
|
14
|
+
DryUI work is explicit. Confirm contracts, build, then validate.
|
|
15
15
|
|
|
16
|
-
1. **User brief** —
|
|
17
|
-
2. **
|
|
18
|
-
3. **
|
|
19
|
-
4. **
|
|
20
|
-
5. **Implementation** — build with DryUI components, Svelte 5, grid layout, tokens, and accessible composition.
|
|
21
|
-
6. **Deterministic check** — run `dryui check [path]` or MCP `check` to catch contracts, accessibility, tokens, and CSS discipline.
|
|
22
|
-
7. **Visual review** — run `dryui check --visual <url>` or MCP `check` with `visualUrl`; include the make-interfaces-feel-better rubric in the review intent.
|
|
23
|
-
8. **Repair loop** — fix the highest-signal issues, then repeat deterministic check and visual review until the UI matches the brief.
|
|
16
|
+
1. **User brief** — one line capturing what you are building and for whom.
|
|
17
|
+
2. **DryUI lookup/plan** — use `dryui ask` or MCP `ask` to confirm component contracts, tokens, recipes, and accessibility notes before choosing components.
|
|
18
|
+
3. **Implementation** — build with DryUI components, Svelte 5 runes, grid layout, `--dry-*` tokens, and accessible composition.
|
|
19
|
+
4. **Deterministic check** — run `dryui check [path]` or MCP `check` to catch contract drift, accessibility regressions, token drift, and CSS discipline violations.
|
|
24
20
|
|
|
25
|
-
|
|
21
|
+
## Design guidance, critique, polish
|
|
26
22
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
23
|
+
DryUI is zero-dependency components + tokens + contracts. It deliberately does NOT ship design opinion. For design-quality flows like brief, critique, polish, visual review, or anti-pattern detection, use [impeccable](https://impeccable.style), which is installed alongside DryUI by `dryui init` or via `npx impeccable skills install`.
|
|
24
|
+
|
|
25
|
+
Invoke from your AI harness:
|
|
26
|
+
|
|
27
|
+
- `/impeccable teach` — one-time: scaffold `PRODUCT.md` + `DESIGN.md`
|
|
28
|
+
- `/impeccable craft` — design-then-build a feature
|
|
29
|
+
- `/impeccable shape` — plan UX/UI before writing code
|
|
30
|
+
- `/impeccable critique <target>` — UX design review
|
|
31
|
+
- `/impeccable audit <target>` — a11y, performance, responsive checks
|
|
32
|
+
- `/impeccable polish <target>` — final pass before shipping
|
|
33
|
+
- Full catalog: https://impeccable.style/cheatsheet
|
|
34
|
+
|
|
35
|
+
`PRODUCT.md` and `DESIGN.md` at the project root are impeccable-owned. DryUI tools do not read or write them. Anti-pattern detection: `npx impeccable detect <path-or-url>`.
|
|
32
36
|
|
|
33
37
|
## 1. Look Up Before You Write
|
|
34
38
|
|
|
35
39
|
**Never guess a component API. Always verify first.**
|
|
36
40
|
|
|
37
|
-
- Call `dryui
|
|
38
|
-
- Component APIs vary
|
|
39
|
-
- Compound vs simple, required parts, available props — all differ per component
|
|
40
|
-
- If you skip the lookup, you'll write plausible-looking code that silently breaks
|
|
41
|
+
- Call `dryui ask --scope component "<Component>"` or `dryui ask --scope recipe "<pattern>"` before using any component for the first time. MCP `ask` is the equivalent surface.
|
|
42
|
+
- Component APIs vary. `bind:value`, `bind:open`, `bind:checked` are NOT interchangeable.
|
|
43
|
+
- Compound vs simple, required parts, available props — all differ per component.
|
|
44
|
+
- If you skip the lookup, you'll write plausible-looking code that silently breaks.
|
|
41
45
|
|
|
42
|
-
The test: can you point to a `dryui
|
|
46
|
+
The test: can you point to a `dryui ask` or MCP `ask` call for every component or pattern in your output?
|
|
43
47
|
|
|
44
48
|
## 2. Everything is Compound Until Proven Otherwise
|
|
45
49
|
|
|
46
50
|
**Use `.Root`. Always check.**
|
|
47
51
|
|
|
48
|
-
Most DryUI components are compound
|
|
52
|
+
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`.
|
|
49
53
|
|
|
50
54
|
```svelte
|
|
51
55
|
<!-- Wrong -->
|
|
@@ -54,7 +58,7 @@ Most DryUI components are compound — they require `<Card.Root>`, not `<Card>`.
|
|
|
54
58
|
<Card.Root>content</Card.Root>
|
|
55
59
|
```
|
|
56
60
|
|
|
57
|
-
Compound components are tracked in the manifest at `packages/mcp/src/component-catalog.ts`. Verify with `
|
|
61
|
+
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.
|
|
58
62
|
|
|
59
63
|
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.
|
|
60
64
|
|
|
@@ -62,11 +66,11 @@ The test: every compound component in your markup uses `.Root`, and its parts ar
|
|
|
62
66
|
|
|
63
67
|
**Import it. Use its tokens. Don't fight it.**
|
|
64
68
|
|
|
65
|
-
- Import `@dryui/ui/themes/default.css` (and `dark.css`) before any component use
|
|
66
|
-
- Use `--dry-color-*` and `--dry-space-*` tokens
|
|
67
|
-
- Don't add decorative CSS (gradients, shadows, colored borders)
|
|
68
|
-
- Override semantic tokens (Tier 2) in `:root`, not component tokens (Tier 3)
|
|
69
|
-
- Prefer `<html class="theme-auto"
|
|
69
|
+
- Import `@dryui/ui/themes/default.css` (and `dark.css`) before any component use.
|
|
70
|
+
- Use `--dry-color-*` and `--dry-space-*` tokens. Never hardcode colors or spacing.
|
|
71
|
+
- Don't add decorative CSS (gradients, shadows, colored borders). The theme handles appearance.
|
|
72
|
+
- Override semantic tokens (Tier 2) in `:root`, not component tokens (Tier 3).
|
|
73
|
+
- Prefer `<html class="theme-auto">`. Use `data-theme="light|dark"` only for explicit overrides.
|
|
70
74
|
|
|
71
75
|
```css
|
|
72
76
|
/* Wrong */
|
|
@@ -84,14 +88,16 @@ The test: every compound component in your markup uses `.Root`, and its parts ar
|
|
|
84
88
|
|
|
85
89
|
The test: does your CSS contain zero hex colors, zero `rgb()` values, and zero inline styles?
|
|
86
90
|
|
|
91
|
+
Theming precedence beats design opinion. If impeccable guidance conflicts with DryUI theme contracts, tokens, or accessibility rules, DryUI wins.
|
|
92
|
+
|
|
87
93
|
## 4. Grid for Layout. Container for Width. @container for Responsive.
|
|
88
94
|
|
|
89
95
|
**Nothing else.**
|
|
90
96
|
|
|
91
|
-
- All layout is `display: grid` with `--dry-space-*` tokens in scoped `<style
|
|
92
|
-
- `Container` (simple component, no `.Root`) for constrained content width
|
|
93
|
-
- Use `@container` queries for responsive sizing
|
|
94
|
-
- No flexbox. No inline styles. No `width`/`min-width`/`max-width` properties
|
|
97
|
+
- All layout is `display: grid` with `--dry-space-*` tokens in scoped `<style>`.
|
|
98
|
+
- `Container` (simple component, no `.Root`) for constrained content width.
|
|
99
|
+
- Use `@container` queries for responsive sizing. Never `@media` for layout breakpoints.
|
|
100
|
+
- No flexbox. No inline styles. No `width`/`min-width`/`max-width` properties.
|
|
95
101
|
|
|
96
102
|
```svelte
|
|
97
103
|
<div class="layout">...</div>
|
|
@@ -104,29 +110,29 @@ The test: does your CSS contain zero hex colors, zero `rgb()` values, and zero i
|
|
|
104
110
|
</style>
|
|
105
111
|
```
|
|
106
112
|
|
|
107
|
-
The test: grep your output for `display: flex`, `style=`, `@media
|
|
113
|
+
The test: grep your output for `display: flex`, `style=`, `@media`. All should return nothing.
|
|
108
114
|
|
|
109
115
|
## 4A. Escape Hatches Mean Stop.
|
|
110
116
|
|
|
111
117
|
**If lint or the compiler pushes you toward an escape hatch, the structure is usually wrong.**
|
|
112
118
|
|
|
113
|
-
- Never add `:global()`, `!important`, `all: unset`, `<svelte:element>`, or `<!-- svelte-ignore ... -->` just to make a selector or warning go away
|
|
114
|
-
- Never add `width`, `min-width`, `max-width`, `inline-size`, `min-inline-size`, or `max-inline-size` to solve layout pressure
|
|
115
|
-
- Never use raw native elements outside their canonical DryUI component directories just because composition feels inconvenient
|
|
116
|
-
- Never pass `class=` to DryUI components expecting it to style their internals
|
|
117
|
-
- When blocked, restructure the markup instead: add a local wrapper, split explicit `{#if}` branches, move sizing to parent grid tracks, or promote the pattern into the canonical component where the raw element belongs
|
|
118
|
-
- Treat `dryui/no-global`, `dryui/no-important`, `dryui/no-width`, `dryui/no-raw-native-element`, `dryui/no-css-ignore`, and `dryui/no-svelte-element` as design feedback, not obstacles to suppress
|
|
119
|
+
- Never add `:global()`, `!important`, `all: unset`, `<svelte:element>`, or `<!-- svelte-ignore ... -->` just to make a selector or warning go away.
|
|
120
|
+
- Never add `width`, `min-width`, `max-width`, `inline-size`, `min-inline-size`, or `max-inline-size` to solve layout pressure.
|
|
121
|
+
- Never use raw native elements outside their canonical DryUI component directories just because composition feels inconvenient.
|
|
122
|
+
- Never pass `class=` to DryUI components expecting it to style their internals. Use wrapper elements, component props, `data-*` attributes, or `--dry-*` tokens instead.
|
|
123
|
+
- When blocked, restructure the markup instead: add a local wrapper, split explicit `{#if}` branches, move sizing to parent grid tracks, or promote the pattern into the canonical component where the raw element belongs.
|
|
124
|
+
- Treat `dryui/no-global`, `dryui/no-important`, `dryui/no-width`, `dryui/no-raw-native-element`, `dryui/no-css-ignore`, and `dryui/no-svelte-element` as design feedback, not obstacles to suppress.
|
|
119
125
|
|
|
120
|
-
The test: grep your output for `:global(`, `!important`, `all: unset`, `svelte-ignore`, `svelte:element`, raw `<button`, raw `<input`, raw `<select`, raw `<dialog`, raw `<hr`, raw `<table`, and `width
|
|
126
|
+
The test: grep your output for `:global(`, `!important`, `all: unset`, `svelte-ignore`, `svelte:element`, raw `<button`, raw `<input`, raw `<select`, raw `<dialog`, raw `<hr`, raw `<table`, and `width:`. All should return nothing unless you are editing the canonical component that owns that native element.
|
|
121
127
|
|
|
122
128
|
## 5. Every Input Gets a Field.Root
|
|
123
129
|
|
|
124
130
|
**Accessibility isn't optional.**
|
|
125
131
|
|
|
126
|
-
- Wrap every form input in `Field.Root` with a `Label
|
|
127
|
-
- Use `AlertDialog` (not `Dialog`) for destructive confirmations
|
|
128
|
-
- Add `aria-label` to every icon-only button
|
|
129
|
-
- Use `type="submit"` on primary form action buttons
|
|
132
|
+
- Wrap every form input in `Field.Root` with a `Label`.
|
|
133
|
+
- Use `AlertDialog` (not `Dialog`) for destructive confirmations.
|
|
134
|
+
- Add `aria-label` to every icon-only button.
|
|
135
|
+
- Use `type="submit"` on primary form action buttons.
|
|
130
136
|
|
|
131
137
|
```svelte
|
|
132
138
|
<!-- Wrong -->
|
|
@@ -146,9 +152,9 @@ The test: every `<Input>`, `<Select.Root>`, `<Textarea>` is inside a `Field.Root
|
|
|
146
152
|
|
|
147
153
|
**If a DryUI component exists for it, use it.**
|
|
148
154
|
|
|
149
|
-
`DatePicker` not `<input type="date">`. `Select.Root` not `<select>`. `Dialog.Root` not `<dialog>`. `Separator` not `<hr>`. `Button` not `<button>`. DryUI components handle theming and accessibility automatically
|
|
155
|
+
`DatePicker` not `<input type="date">`. `Select.Root` not `<select>`. `Dialog.Root` not `<dialog>`. `Separator` not `<hr>`. `Button` not `<button>`. DryUI components handle theming and accessibility automatically. Native elements don't.
|
|
150
156
|
|
|
151
|
-
The test: search your markup for raw `<input`, `<select>`, `<dialog>`, `<button>`, `<hr>`, `<table
|
|
157
|
+
The test: search your markup for raw `<input`, `<select>`, `<dialog>`, `<button>`, `<hr>`, `<table>`. Each should be a DryUI component instead.
|
|
152
158
|
|
|
153
159
|
## 7. Ask the Svelte MCP for Svelte Questions
|
|
154
160
|
|
|
@@ -156,7 +162,7 @@ The test: search your markup for raw `<input`, `<select>`, `<dialog>`, `<button>
|
|
|
156
162
|
|
|
157
163
|
For Svelte 5 runes (`$state`, `$derived`, `$effect`, `$props`), snippets, SvelteKit load fns, `+page.server.ts` shape, form actions, and anything Svelte-syntax adjacent: call the official `svelte-autofixer` and `get-documentation` tools from `@sveltejs/mcp` before guessing from memory.
|
|
158
164
|
|
|
159
|
-
- `dryui setup --install` registers `@sveltejs/mcp` by default
|
|
165
|
+
- `dryui setup --install` registers `@sveltejs/mcp` by default. Pass `--no-svelte-mcp` to skip.
|
|
160
166
|
- If it's not registered, the fallback is the remote endpoint `https://mcp.svelte.dev/mcp` or a one-liner like `claude mcp add -t stdio -s user svelte -- npx -y @sveltejs/mcp`.
|
|
161
167
|
- Scope split: DryUI `ask`/`check` cover component APIs, theming, composition, and validation. Svelte MCP covers the runtime, compiler, and framework idioms.
|
|
162
168
|
|
|
@@ -180,13 +186,13 @@ dryui
|
|
|
180
186
|
|
|
181
187
|
```bash
|
|
182
188
|
dryui init # existing project
|
|
183
|
-
dryui init my-app # new project
|
|
189
|
+
dryui init my-app # new project, scaffolds SvelteKit + DryUI in one step
|
|
184
190
|
cd my-app && bun run dev
|
|
185
191
|
```
|
|
186
192
|
|
|
187
193
|
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.
|
|
188
194
|
|
|
189
|
-
> **No global install?** `bunx @dryui/cli <cmd>` and `npx -y @dryui/cli <cmd>` work anywhere without installing
|
|
195
|
+
> **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).
|
|
190
196
|
|
|
191
197
|
**4. Add the editor integration layer** after the CLI is working:
|
|
192
198
|
|
|
@@ -195,12 +201,12 @@ This works for greenfield (empty directory), brownfield (existing non-SvelteKit
|
|
|
195
201
|
- OpenCode: `npx degit rob-balfre/dryui/packages/ui/skills/dryui .opencode/skills/dryui` + add the `dryui` and `dryui-feedback` local MCP servers in `opencode.json` (OpenCode also loads `.agents/skills/dryui` and reads `AGENTS.md`)
|
|
196
202
|
- Copilot/Cursor/Windsurf: `npx degit rob-balfre/dryui/packages/ui/skills/dryui .agents/skills/dryui` + add MCP config (see https://dryui.dev/tools)
|
|
197
203
|
|
|
198
|
-
**5. Register the Svelte MCP companion.** `dryui setup --install` does this automatically for Copilot, Cursor, OpenCode, Windsurf, and Zed. For Claude Code run `claude mcp add -t stdio -s user svelte -- npx -y @sveltejs/mcp
|
|
204
|
+
**5. Register the Svelte MCP companion.** `dryui setup --install` does this automatically for Copilot, Cursor, OpenCode, Windsurf, and Zed. For Claude Code run `claude mcp add -t stdio -s user svelte -- npx -y @sveltejs/mcp`. For Codex add `[mcp_servers.svelte] command = "npx", args = ["-y", "@sveltejs/mcp"]` to `~/.codex/config.toml`. See rule 7 above.
|
|
199
205
|
|
|
200
206
|
### Manual setup
|
|
201
207
|
|
|
202
208
|
1. `bun add @dryui/ui`
|
|
203
|
-
2. `bun add -d @dryui/lint
|
|
209
|
+
2. `bun add -d @dryui/lint`. Enforces grid-only layout, bans flexbox/inline-style/width at build time. Without this step the CSS discipline rules are not enforced at build time, and only post-write `check` / CLI validation remain.
|
|
204
210
|
3. Wire the lint preprocessor in `svelte.config.js` (add `dryuiLint` as the **first** item in the `preprocess` array):
|
|
205
211
|
|
|
206
212
|
```js
|
|
@@ -220,7 +226,7 @@ This works for greenfield (empty directory), brownfield (existing non-SvelteKit
|
|
|
220
226
|
export default config;
|
|
221
227
|
```
|
|
222
228
|
|
|
223
|
-
4. Add `class="theme-auto"` to `<html>` in `src/app.html
|
|
229
|
+
4. Add `class="theme-auto"` to `<html>` in `src/app.html`.
|
|
224
230
|
5. In root layout (`src/routes/+layout.svelte`), import themes:
|
|
225
231
|
```svelte
|
|
226
232
|
<script>
|
|
@@ -228,18 +234,18 @@ This works for greenfield (empty directory), brownfield (existing non-SvelteKit
|
|
|
228
234
|
import '@dryui/ui/themes/dark.css';
|
|
229
235
|
</script>
|
|
230
236
|
```
|
|
231
|
-
6. Import `app.css` AFTER theme CSS if you have custom styles
|
|
237
|
+
6. Import `app.css` AFTER theme CSS if you have custom styles.
|
|
232
238
|
|
|
233
|
-
> `dryui init` applies all six steps automatically
|
|
239
|
+
> `dryui init` applies all six steps automatically. Prefer it over manual setup when you can.
|
|
234
240
|
|
|
235
|
-
## Bindable Props
|
|
241
|
+
## Bindable Props, Common Confusion
|
|
236
242
|
|
|
237
|
-
Always verify with `
|
|
243
|
+
Always verify with `ask --scope component`, but these are the most common mistakes:
|
|
238
244
|
|
|
239
245
|
- `bind:value` (Input, Select, Tabs...) vs `bind:checked` (Checkbox, Switch) vs `bind:pressed` (Toggle) vs `bind:open` (Dialog, Popover, Drawer...)
|
|
240
|
-
- Select and Combobox support both `bind:value` and `bind:open
|
|
241
|
-
- ColorPicker also exposes `bind:alpha
|
|
242
|
-
- Tour uses `bind:active`, not `bind:open
|
|
246
|
+
- Select and Combobox support both `bind:value` and `bind:open`.
|
|
247
|
+
- ColorPicker also exposes `bind:alpha`. Transfer uses `bind:sourceItems` / `bind:targetItems`.
|
|
248
|
+
- Tour uses `bind:active`, not `bind:open`.
|
|
243
249
|
|
|
244
250
|
## Tools
|
|
245
251
|
|
|
@@ -247,12 +253,10 @@ Use these to look up APIs, discover components, plan setup, and validate code.
|
|
|
247
253
|
|
|
248
254
|
### Recommended workflow
|
|
249
255
|
|
|
250
|
-
1.
|
|
251
|
-
2.
|
|
252
|
-
3.
|
|
253
|
-
4.
|
|
254
|
-
5. Run `dryui check --visual <url>` or MCP `check` with `visualUrl` for rendered review, then repair and repeat until the UI satisfies the brief.
|
|
255
|
-
6. Never guess component shape from memory. DryUI is intentionally strict, and the lookup cost is lower than rework.
|
|
256
|
+
1. Resolve any component or recipe uncertainty with `dryui ask --scope component "<Component>"` or `dryui ask --scope recipe "<pattern>"`. If MCP is available, `ask --scope component` and `ask --scope recipe` are the equivalent surface.
|
|
257
|
+
2. Build the route or component with raw CSS grid, `Container` for constrained width, and `@container` for responsive layout.
|
|
258
|
+
3. Run `dryui check [path]` or MCP `check` after implementation to catch composition drift, layout violations, accessibility regressions, and token drift.
|
|
259
|
+
4. Never guess component shape from memory. DryUI is intentionally strict, and the lookup cost is lower than rework.
|
|
256
260
|
|
|
257
261
|
### CLI (default entry point)
|
|
258
262
|
|
|
@@ -262,12 +266,10 @@ Install once with `bun install -g @dryui/cli@latest` (or `npm install -g @dryui/
|
|
|
262
266
|
dryui # default onboarding entry point
|
|
263
267
|
dryui setup # explicit onboarding subcommand
|
|
264
268
|
dryui init [path] [--pm bun] # Bootstrap SvelteKit + DryUI project
|
|
265
|
-
dryui
|
|
266
|
-
dryui compose "date input" # Composition guidance
|
|
269
|
+
dryui ask <scope> "<query>" # Look up components, recipes, tokens, setup
|
|
267
270
|
dryui detect [path] # Check project setup
|
|
268
271
|
dryui install [path] # Print install plan
|
|
269
272
|
dryui check [path] # Validate file, theme, directory, or workspace
|
|
270
|
-
dryui check --visual <url> # Screenshot a URL and critique rendered polish
|
|
271
273
|
dryui list # List components
|
|
272
274
|
dryui tokens --category color # Browse design tokens
|
|
273
275
|
dryui ambient # SessionStart context
|
|
@@ -275,7 +277,7 @@ dryui install-hook --dry-run # Preview Claude hook wiring
|
|
|
275
277
|
dryui feedback init # Feedback tooling setup
|
|
276
278
|
```
|
|
277
279
|
|
|
278
|
-
Without a global install, prefix any command with `bunx @dryui/cli …` or `npx -y @dryui/cli
|
|
280
|
+
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).
|
|
279
281
|
|
|
280
282
|
### MCP tools (same workflow in-editor)
|
|
281
283
|
|
|
@@ -285,7 +287,6 @@ Without a global install, prefix any command with `bunx @dryui/cli …` or `npx
|
|
|
285
287
|
| Lookup & composition | `ask --scope component`, `ask --scope recipe`, `ask --scope list` |
|
|
286
288
|
| Validation | `check <file.svelte>`, `check <theme.css>` |
|
|
287
289
|
| Audit | `check`, `check <directory>` |
|
|
288
|
-
| Rendered UI | `check` with `visualUrl`, or direct `check-vision` |
|
|
289
290
|
|
|
290
291
|
Categories: action, input, form, layout, navigation, overlay, display, feedback, interaction, utility
|
|
291
292
|
|
|
@@ -298,11 +299,8 @@ Read these when you need deeper guidance:
|
|
|
298
299
|
- **`rules/composition.md`** — Form patterns, page layouts, composition recipes
|
|
299
300
|
- **`rules/accessibility.md`** — Field.Root, ARIA, focus management, pre-ship checklist
|
|
300
301
|
- **`rules/svelte.md`** — Runes, snippets, native browser APIs, styling rules
|
|
301
|
-
- **`rules/design.md`** — Minimal code, no premature abstraction, naming conventions
|
|
302
|
-
- **`rules/design-brief.md`** — User brief, DESIGN.md identity, precedence, and polish review pipeline
|
|
303
|
-
- **`rules/visual-effects-performance.md`** — Tiered budgets and implementation rules for shader, blur, glass, and pointer-reactive effects
|
|
304
302
|
- **`rules/native-web-transitions.md`** — View Transition API, scroll animations, reduced-motion
|
|
305
303
|
|
|
306
304
|
---
|
|
307
305
|
|
|
308
|
-
**These rules are working if:** every component traces to a lookup, diffs contain zero hardcoded colors, and
|
|
306
|
+
**These rules are working if:** every component traces to a lookup, diffs contain zero hardcoded colors, and `dryui check` finds nothing.
|
|
@@ -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 `dryui
|
|
401
|
+
Before using any component, call `dryui ask --scope recipe "<pattern>"` (for layouts) or `dryui ask --scope component "<Component>"` (for APIs) to get the correct component and usage snippet. 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 `dryui compose "<query>"` or `dryui info <Compo
|
|
|
431
431
|
|
|
432
432
|
## Composition Recipes
|
|
433
433
|
|
|
434
|
-
Call `dryui
|
|
434
|
+
Call `dryui ask --scope recipe "<recipe>"` with any recipe name to get a full working snippet.
|
|
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 `dryui
|
|
460
|
+
- Run `dryui ask --scope component "<Component>"` or `dryui ask --scope recipe "<pattern>"` before introducing a new field shape, then run `dryui check [path]` or MCP `check` after the flow is wired.
|
|
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. Prefer `dryui
|
|
21
|
+
Below are the parts for the most commonly used compound components. Prefer `dryui ask --scope component "<name>"` for the full, up-to-date parts list.
|
|
22
22
|
|
|
23
23
|
### Card
|
|
24
24
|
|
|
@@ -307,6 +307,6 @@ Parts: Root, Input, Content, Item, Empty
|
|
|
307
307
|
|
|
308
308
|
## Full Compound Component List
|
|
309
309
|
|
|
310
|
-
Run `dryui
|
|
310
|
+
Run `dryui ask --scope component "<name>"` for any component's complete parts list:
|
|
311
311
|
|
|
312
312
|
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
|
|
@@ -80,7 +80,7 @@ Always include a reduced-motion override:
|
|
|
80
80
|
}
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
-
The `@media` block comes after the default rule in the same scoped stylesheet, so equal specificity plus source order handles the override without `!important`
|
|
83
|
+
The `@media` block comes after the default rule in the same scoped stylesheet, so equal specificity plus source order handles the override without `!important` (banned by `@dryui/lint` via `dryui/no-important`). In JS, skip delayed typewriter/replay steps and render the final state immediately when reduced motion is active.
|
|
84
84
|
|
|
85
85
|
## Svelte notes
|
|
86
86
|
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# Design Brief Pipeline
|
|
2
|
-
|
|
3
|
-
Use this rule before building or materially changing a UI. It keeps intent, component contracts, and polish review in the same visible loop.
|
|
4
|
-
|
|
5
|
-
## Pipeline
|
|
6
|
-
|
|
7
|
-
1. **User brief** — capture the audience, primary job, product/domain, density, tone, constraints, and success criteria. Ask only for missing information that changes the implementation.
|
|
8
|
-
2. **DESIGN.md identity** — read the project `DESIGN.md` if present. If the project lacks one and the UI has meaningful visual direction, draft the identity in the working notes or create the file when the user asks for durable design guidance. Google-style `DESIGN.md` is an optional supported format, not a dependency.
|
|
9
|
-
3. **DryUI lookup/plan** — use `dryui info`, `dryui compose`, or MCP `ask` before selecting components or recipes. Plan around confirmed component contracts, accessibility, tokens, and grid layout.
|
|
10
|
-
4. **make-interfaces-feel-better polish intent pass** — explicitly list the polish details that apply before implementation. This is a planning step, not hidden preference.
|
|
11
|
-
5. **Implementation** — build with DryUI components, Svelte 5, scoped grid CSS, tokenized styling, and accessible composition.
|
|
12
|
-
6. **Deterministic check** — run `dryui check [path]` or MCP `check` against edited files or the workspace.
|
|
13
|
-
7. **Visual review** — run `dryui check --visual <url>` or MCP `check` with `visualUrl`. Name the user brief and the polish intent in the review prompt when possible.
|
|
14
|
-
8. **Repair loop** — fix issues in priority order, then repeat deterministic check and visual review until the screen satisfies the brief.
|
|
15
|
-
|
|
16
|
-
## Precedence
|
|
17
|
-
|
|
18
|
-
When guidance conflicts, use this order:
|
|
19
|
-
|
|
20
|
-
1. User intent and explicit task constraints.
|
|
21
|
-
2. Local `DESIGN.md` identity.
|
|
22
|
-
3. DryUI component contracts, accessibility rules, and token/theming discipline.
|
|
23
|
-
4. Official Svelte MCP guidance for Svelte and SvelteKit syntax/framework decisions.
|
|
24
|
-
5. make-interfaces-feel-better polish rubric.
|
|
25
|
-
|
|
26
|
-
## Brief Shape
|
|
27
|
-
|
|
28
|
-
Keep the brief short and implementation-oriented:
|
|
29
|
-
|
|
30
|
-
- Who is using this?
|
|
31
|
-
- What are they trying to do?
|
|
32
|
-
- What information or action must be visible first?
|
|
33
|
-
- How dense should the interface be?
|
|
34
|
-
- What brand, product, or domain cues should shape the identity?
|
|
35
|
-
- What constraints are fixed by the user, existing code, or platform?
|
|
36
|
-
|
|
37
|
-
## DESIGN.md Identity
|
|
38
|
-
|
|
39
|
-
Use `DESIGN.md` to preserve durable identity decisions: product personality, visual tone, density, navigation model, content hierarchy, color/token direction, motion posture, and examples of what to avoid.
|
|
40
|
-
|
|
41
|
-
Do not treat `DESIGN.md` as more important than the user's current request. If the user changes direction, follow the user and update the identity only when the change is meant to persist.
|
|
42
|
-
|
|
43
|
-
## Polish Intent
|
|
44
|
-
|
|
45
|
-
The make-interfaces-feel-better pass should be explicit in planning and visual review. Check the applicable details:
|
|
46
|
-
|
|
47
|
-
- balanced headings and pretty body wrapping
|
|
48
|
-
- concentric radius for nested surfaces
|
|
49
|
-
- icon swaps that crossfade without jitter
|
|
50
|
-
- tabular numbers for counters, prices, clocks, and scores
|
|
51
|
-
- transitions for interactive state, not keyframe-only state changes
|
|
52
|
-
- staggered entrances when groups appear
|
|
53
|
-
- smaller exits than entrances
|
|
54
|
-
- shadow treatment for raised surfaces
|
|
55
|
-
- optical icon alignment inside buttons
|
|
56
|
-
- adaptive image edges for media and avatars
|
|
57
|
-
- token consistency across color, radius, shadow, spacing, duration, and easing
|
|
58
|
-
|
|
59
|
-
If a detail does not apply, skip it deliberately. Polish is a review rubric, not decoration.
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
# Clean Code Standards
|
|
2
|
-
|
|
3
|
-
## Core Principle
|
|
4
|
-
|
|
5
|
-
Write the minimum correct code. Every line must earn its place.
|
|
6
|
-
|
|
7
|
-
## Rules
|
|
8
|
-
|
|
9
|
-
### No Premature Abstraction
|
|
10
|
-
|
|
11
|
-
- Three similar lines > one premature helper
|
|
12
|
-
- Only extract when you have 3+ real call sites
|
|
13
|
-
- No "just in case" configurability, feature flags, or extension points
|
|
14
|
-
- Delete code you don't need -- don't comment it out
|
|
15
|
-
|
|
16
|
-
### No Noise
|
|
17
|
-
|
|
18
|
-
- No comments that restate what code does
|
|
19
|
-
- Comments only where the _why_ is non-obvious
|
|
20
|
-
- No empty catch blocks -- handle or rethrow
|
|
21
|
-
- No unused imports, variables, or parameters
|
|
22
|
-
- No `console.log` left in production code
|
|
23
|
-
- No commented-out code -- use git history
|
|
24
|
-
|
|
25
|
-
### Names Are Documentation
|
|
26
|
-
|
|
27
|
-
- Functions: verb + noun (`getUser`, `handleClick`, `parseDate`)
|
|
28
|
-
- Booleans: `is`/`has`/`should` prefix (`isOpen`, `hasError`)
|
|
29
|
-
- Collections: plural (`users`, `items`)
|
|
30
|
-
- Callbacks: `on` + event (`onClose`, `onChange`)
|
|
31
|
-
- Constants: UPPER_SNAKE only for true compile-time constants
|
|
32
|
-
|
|
33
|
-
### Functions
|
|
34
|
-
|
|
35
|
-
- One job per function
|
|
36
|
-
- Max 3 parameters -- use an options object for more
|
|
37
|
-
- Return early to avoid nesting
|
|
38
|
-
- Pure functions where possible -- side effects at the edges
|
|
39
|
-
|
|
40
|
-
### Error Handling
|
|
41
|
-
|
|
42
|
-
- Only validate at system boundaries (user input, API responses, file reads)
|
|
43
|
-
- Trust internal code -- don't null-check values you just created
|
|
44
|
-
- Use specific error types, not generic strings
|
|
45
|
-
- Handle errors at the level that can meaningfully respond
|
|
46
|
-
|
|
47
|
-
### File Organization
|
|
48
|
-
|
|
49
|
-
- One concept per file
|
|
50
|
-
- Keep files under 300 lines -- split if growing
|
|
51
|
-
- Colocate related code (component + styles + tests in same directory)
|
|
52
|
-
- Index files only for re-exports, never for logic
|
|
53
|
-
|
|
54
|
-
## Anti-Patterns -- Stop and Fix
|
|
55
|
-
|
|
56
|
-
| If you're about to... | Instead... |
|
|
57
|
-
| --------------------------------------- | ------------------------------------------------------------ |
|
|
58
|
-
| Add a "utils" file | Put it where it's used (shared module only at 3+ call sites) |
|
|
59
|
-
| Create a base class | Use composition |
|
|
60
|
-
| Add a config option nobody asked for | Don't |
|
|
61
|
-
| Write a comment explaining _what_ | Rename so it's obvious |
|
|
62
|
-
| Add error handling for impossible cases | Trust internal code |
|
|
63
|
-
| Create a wrapper around a simple API | Use the API directly |
|
|
64
|
-
|
|
65
|
-
## 5 Rules of Programming
|
|
66
|
-
|
|
67
|
-
1. You can't tell where a program is going to spend its time. Bottlenecks occur in surprising places.
|
|
68
|
-
2. Measure. Don't tune for speed until you've measured.
|
|
69
|
-
3. Fancy algorithms are slow when n is small, and n is usually small.
|
|
70
|
-
4. Fancy algorithms are buggier than simple ones. Use simple algorithms and simple data structures.
|
|
71
|
-
5. Data dominates. If you've chosen the right data structures, the algorithms will be self-evident.
|