@naeil/ui 0.2.0 → 0.4.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.
Files changed (109) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/DESIGN.md +194 -0
  3. package/LICENSE +21 -0
  4. package/README.md +77 -53
  5. package/THIRD_PARTY_NOTICES.md +51 -0
  6. package/dist/{chunk-SYI3IKIC.js → chunk-QKVN6Q6C.js} +102 -19
  7. package/dist/components/index.d.ts +17 -8
  8. package/dist/components/index.js +1278 -251
  9. package/dist/components/ui/index.d.ts +27 -5
  10. package/dist/components/ui/index.js +288 -33
  11. package/dist/source/components/brand-artwork.generated.d.ts +162 -0
  12. package/dist/source/components/brand-artwork.generated.js +1103 -0
  13. package/dist/source/components/footer.d.ts +15 -0
  14. package/dist/source/components/footer.js +128 -0
  15. package/dist/source/components/index.d.ts +11 -0
  16. package/dist/source/components/index.js +26 -0
  17. package/dist/source/components/locale-switcher.d.ts +17 -0
  18. package/dist/source/components/locale-switcher.js +66 -0
  19. package/dist/source/components/logo.d.ts +18 -0
  20. package/dist/source/components/logo.js +91 -0
  21. package/dist/source/components/nav.d.ts +39 -0
  22. package/dist/source/components/nav.js +375 -0
  23. package/dist/source/components/theme-provider.d.ts +6 -0
  24. package/dist/source/components/theme-provider.js +45 -0
  25. package/dist/source/components/theme-toggle-icon.d.ts +5 -0
  26. package/dist/source/components/theme-toggle-icon.js +62 -0
  27. package/dist/source/components/theme-toggle.d.ts +5 -0
  28. package/dist/source/components/theme-toggle.js +34 -0
  29. package/dist/source/components/typography.d.ts +12 -0
  30. package/dist/source/components/typography.js +49 -0
  31. package/dist/source/components/ui/avatar.d.ts +14 -0
  32. package/dist/source/components/ui/avatar.js +137 -0
  33. package/dist/source/components/ui/badge.d.ts +13 -0
  34. package/dist/source/components/ui/badge.js +76 -0
  35. package/dist/source/components/ui/button.d.ts +14 -0
  36. package/dist/source/components/ui/button.js +88 -0
  37. package/dist/source/components/ui/card.d.ts +13 -0
  38. package/dist/source/components/ui/card.js +135 -0
  39. package/dist/source/components/ui/checkbox.d.ts +7 -0
  40. package/dist/source/components/ui/checkbox.js +58 -0
  41. package/dist/source/components/ui/dialog.d.ts +20 -0
  42. package/dist/source/components/ui/dialog.js +182 -0
  43. package/dist/source/components/ui/dropdown-menu.d.ts +28 -0
  44. package/dist/source/components/ui/dropdown-menu.js +282 -0
  45. package/dist/source/components/ui/index.d.ts +19 -0
  46. package/dist/source/components/ui/index.js +131 -0
  47. package/dist/source/components/ui/input.d.ts +6 -0
  48. package/dist/source/components/ui/input.js +49 -0
  49. package/dist/source/components/ui/select.d.ts +16 -0
  50. package/dist/source/components/ui/select.js +197 -0
  51. package/dist/source/components/ui/sonner.d.ts +6 -0
  52. package/dist/source/components/ui/sonner.js +80 -0
  53. package/dist/source/components/ui/switch.d.ts +7 -0
  54. package/dist/source/components/ui/switch.js +54 -0
  55. package/dist/source/components/ui/tabs.d.ts +10 -0
  56. package/dist/source/components/ui/tabs.js +57 -0
  57. package/dist/source/components/ui/textarea.d.ts +6 -0
  58. package/dist/source/components/ui/textarea.js +46 -0
  59. package/dist/source/i18n/config.d.ts +6 -0
  60. package/dist/source/i18n/config.js +12 -0
  61. package/dist/source/i18n/routing.d.ts +371 -0
  62. package/dist/source/i18n/routing.js +17 -0
  63. package/dist/source/lib/design/modal-inert.d.ts +6 -0
  64. package/dist/source/lib/design/modal-inert.js +73 -0
  65. package/dist/source/lib/utils.d.ts +5 -0
  66. package/dist/source/lib/utils.js +8 -0
  67. package/dist/tokens.ts +217 -0
  68. package/docs/components/README.md +21 -0
  69. package/docs/components/avatar.md +47 -0
  70. package/docs/components/badge.md +29 -0
  71. package/docs/components/button.md +39 -0
  72. package/docs/components/card.md +32 -0
  73. package/docs/components/checkbox.md +35 -0
  74. package/docs/components/common.md +31 -0
  75. package/docs/components/composition.md +68 -0
  76. package/docs/components/dialog.md +41 -0
  77. package/docs/components/dropdown-menu.md +38 -0
  78. package/docs/components/input.md +32 -0
  79. package/docs/components/select.md +39 -0
  80. package/docs/components/sonner.md +37 -0
  81. package/docs/components/switch.md +33 -0
  82. package/docs/components/tabs.md +40 -0
  83. package/docs/components/textarea.md +35 -0
  84. package/docs/design/component-demand.md +30 -0
  85. package/docs/design/public-ui-final-verification.md +96 -0
  86. package/docs/design/public-ui-release-validation.md +86 -0
  87. package/docs/design/public-ui-stage3-verification.md +98 -0
  88. package/docs/design/public-ui-stage4-verification.md +129 -0
  89. package/docs/design/public-ui-support.md +42 -0
  90. package/docs/design/v2-migration.md +229 -0
  91. package/docs/package-release.md +108 -0
  92. package/licenses/Lato-OFL.txt +93 -0
  93. package/licenses/Noto-Sans-JP-OFL.txt +93 -0
  94. package/licenses/Nunito-Sans-OFL.txt +93 -0
  95. package/licenses/Pretendard-OFL.txt +94 -0
  96. package/licenses/React-MIT.txt +21 -0
  97. package/licenses/Storybook-MIT.txt +21 -0
  98. package/licenses/popper-MIT.txt +25 -0
  99. package/licenses/react-remove-scroll-bar-MIT.txt +21 -0
  100. package/licenses/store2-MIT.txt +22 -0
  101. package/licenses/toggle-selection-MIT.txt +21 -0
  102. package/package.json +184 -34
  103. package/src/styles/components.css +276 -0
  104. package/src/styles/globals.css +18 -0
  105. package/src/styles/theme.css +185 -98
  106. package/messages/en.json +0 -221
  107. package/messages/ja.json +0 -221
  108. package/messages/ko.json +0 -221
  109. package/src/app/globals.css +0 -32
package/dist/tokens.ts ADDED
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Auto-generated by build-tokens.ts — DO NOT EDIT
3
+ */
4
+
5
+ export const color = {
6
+ gray: {
7
+ "50": "oklch(0.985 0 0)",
8
+ "100": "oklch(0.970 0 0)",
9
+ "200": "oklch(0.922 0 0)",
10
+ "300": "oklch(0.870 0 0)",
11
+ "400": "oklch(0.708 0 0)",
12
+ "500": "oklch(0.556 0 0)",
13
+ "600": "oklch(0.439 0 0)",
14
+ "700": "oklch(0.371 0 0)",
15
+ "800": "oklch(0.269 0 0)",
16
+ "900": "oklch(0.205 0 0)",
17
+ "950": "oklch(0.145 0 0)",
18
+ },
19
+ accent: {
20
+ "50": "oklch(0.970 0.014 254)",
21
+ "100": "oklch(0.932 0.032 255)",
22
+ "200": "oklch(0.882 0.059 254)",
23
+ "300": "oklch(0.809 0.105 254)",
24
+ "400": "oklch(0.707 0.165 254)",
25
+ "500": "oklch(0.623 0.214 259)",
26
+ "600": "oklch(0.546 0.245 263)",
27
+ "700": "oklch(0.488 0.243 264.376)",
28
+ "800": "oklch(0.424 0.199 265)",
29
+ "900": "oklch(0.379 0.146 265)",
30
+ "950": "oklch(0.282 0.091 267)",
31
+ },
32
+ destructive: {
33
+ "50": "oklch(0.971 0.013 17)",
34
+ "100": "oklch(0.936 0.032 17)",
35
+ "200": "oklch(0.885 0.062 18)",
36
+ "300": "oklch(0.808 0.114 19)",
37
+ "400": "oklch(0.704 0.191 22.216)",
38
+ "500": "oklch(0.637 0.237 25)",
39
+ "600": "oklch(0.577 0.245 27.325)",
40
+ "700": "oklch(0.505 0.213 27)",
41
+ "800": "oklch(0.444 0.177 26)",
42
+ "900": "oklch(0.396 0.141 25)",
43
+ "950": "oklch(0.258 0.092 26)",
44
+ },
45
+ project: {
46
+ cc: "oklch(0.704 0.140 181)",
47
+ sa: "oklch(0.723 0.219 149)",
48
+ pkm: "oklch(0.627 0.265 303)",
49
+ naeilUi: "oklch(0.623 0.214 259)",
50
+ baby: "oklch(0.769 0.188 70.08)",
51
+ },
52
+ white: "oklch(1 0 0)",
53
+ black: "oklch(0 0 0)",
54
+ } as const;
55
+
56
+ export const font = {
57
+ family: {
58
+ sans: ["Pretendard Variable","Pretendard","Noto Sans JP","-apple-system","system-ui","sans-serif"],
59
+ mono: ["JetBrains Mono","Geist Mono","ui-monospace","monospace"],
60
+ },
61
+ size: {
62
+ "xs": "0.8125rem",
63
+ "sm": "0.875rem",
64
+ "base": "1rem",
65
+ "lg": "1.125rem",
66
+ "xl": "1.25rem",
67
+ "2xl": "1.5rem",
68
+ "3xl": "2rem",
69
+ "4xl": "2.25rem",
70
+ },
71
+ weight: {
72
+ normal: 400,
73
+ medium: 500,
74
+ bold: 700,
75
+ semibold: 600,
76
+ },
77
+ lineHeight: {
78
+ tight: 1.25,
79
+ normal: 1.5625,
80
+ relaxed: 1.875,
81
+ },
82
+ } as const;
83
+
84
+ export const spacing = {
85
+ "0": "0px",
86
+ "1": "4px",
87
+ "2": "8px",
88
+ "3": "12px",
89
+ "4": "16px",
90
+ "5": "20px",
91
+ "6": "24px",
92
+ "8": "32px",
93
+ "10": "40px",
94
+ "12": "48px",
95
+ "16": "64px",
96
+ } as const;
97
+
98
+ export const radius = {
99
+ none: "0px",
100
+ sm: "4px",
101
+ md: "8px",
102
+ lg: "12px",
103
+ } as const;
104
+
105
+ export const shadow = {
106
+ sm: "0 1px 2px 0 oklch(0 0 0 / 0.05)",
107
+ md: "0 4px 6px -1px oklch(0 0 0 / 0.1), 0 2px 4px -2px oklch(0 0 0 / 0.1)",
108
+ } as const;
109
+
110
+ export const semantic = {
111
+ light: {
112
+ "background": "#f7f8fa",
113
+ "foreground": "#202127",
114
+ "card": "#ffffff",
115
+ "card-foreground": "#202127",
116
+ "popover": "#ffffff",
117
+ "popover-foreground": "#202127",
118
+ "primary": "#292929",
119
+ "primary-foreground": "#ffffff",
120
+ "secondary": "#ebebeb",
121
+ "secondary-foreground": "#202127",
122
+ "muted": "#f0f1f5",
123
+ "muted-foreground": "#5f626e",
124
+ "accent": "#ebebeb",
125
+ "accent-foreground": "#202127",
126
+ "destructive": "#b73a45",
127
+ "destructive-foreground": "#ffffff",
128
+ "border": "#e2e3e9",
129
+ "border-subtle": "#e2e3e9",
130
+ "border-subtle-hover": "#8c8f9b",
131
+ "surface-subtle": "#f0f1f5",
132
+ "input": "#8c8f9b",
133
+ "ring": "#616161",
134
+ "chart-1": "oklch(0.646 0.222 41.116)",
135
+ "chart-2": "oklch(0.6 0.118 184.704)",
136
+ "chart-3": "oklch(0.398 0.07 227.392)",
137
+ "chart-4": "oklch(0.828 0.189 84.429)",
138
+ "chart-5": "oklch(0.769 0.188 70.08)",
139
+ "sidebar": "#ffffff",
140
+ "sidebar-foreground": "#202127",
141
+ "sidebar-primary": "#292929",
142
+ "sidebar-primary-foreground": "#ffffff",
143
+ "sidebar-accent": "#ebebeb",
144
+ "sidebar-accent-foreground": "#202127",
145
+ "sidebar-border": "#e2e3e9",
146
+ "sidebar-ring": "#616161",
147
+ "card-elevated": "#ffffff",
148
+ "primary-hover": "#414141",
149
+ "surface-selected": "#ebebeb",
150
+ "border-strong": "#8c8f9b",
151
+ "error": "#b73a45",
152
+ "error-bg": "#fff0f1",
153
+ "success": "#246a49",
154
+ "success-bg": "#e9f5ee",
155
+ "warning": "#865d17",
156
+ "warning-bg": "#fff3da",
157
+ "info": "#245ec7",
158
+ "info-bg": "#edf3ff",
159
+ },
160
+ dark: {
161
+ "background": "#0b0c0e",
162
+ "foreground": "#eff0f3",
163
+ "card": "#111215",
164
+ "card-foreground": "#eff0f3",
165
+ "popover": "#191a1f",
166
+ "popover-foreground": "#eff0f3",
167
+ "primary": "#e5e5e5",
168
+ "primary-foreground": "#171717",
169
+ "secondary": "#262626",
170
+ "secondary-foreground": "#eff0f3",
171
+ "muted": "#202127",
172
+ "muted-foreground": "#a4a6b0",
173
+ "accent": "#262626",
174
+ "accent-foreground": "#eff0f3",
175
+ "destructive": "#b73a45",
176
+ "destructive-foreground": "#ffffff",
177
+ "border": "#292b33",
178
+ "border-subtle": "#292b33",
179
+ "border-subtle-hover": "#646773",
180
+ "surface-subtle": "#202127",
181
+ "input": "#646773",
182
+ "ring": "#b5b5b5",
183
+ "chart-1": "oklch(0.488 0.243 264.376)",
184
+ "chart-2": "oklch(0.696 0.17 162.48)",
185
+ "chart-3": "oklch(0.769 0.188 70.08)",
186
+ "chart-4": "oklch(0.627 0.265 303.9)",
187
+ "chart-5": "oklch(0.645 0.246 16.439)",
188
+ "sidebar": "#111215",
189
+ "sidebar-foreground": "#eff0f3",
190
+ "sidebar-primary": "#e5e5e5",
191
+ "sidebar-primary-foreground": "#171717",
192
+ "sidebar-accent": "#262626",
193
+ "sidebar-accent-foreground": "#eff0f3",
194
+ "sidebar-border": "#292b33",
195
+ "sidebar-ring": "#b5b5b5",
196
+ "card-elevated": "#191a1f",
197
+ "primary-hover": "#cccccc",
198
+ "surface-selected": "#262626",
199
+ "border-strong": "#646773",
200
+ "error": "#f0a0a0",
201
+ "error-bg": "#342021",
202
+ "success": "#87c9a6",
203
+ "success-bg": "#182b23",
204
+ "warning": "#e4bf78",
205
+ "warning-bg": "#302819",
206
+ "info": "#a2c2ff",
207
+ "info-bg": "#18263f",
208
+ },
209
+ } as const;
210
+
211
+ export type ColorScale = keyof typeof color.gray;
212
+ export type AccentScale = keyof typeof color.accent;
213
+ export type SemanticToken = keyof typeof semantic.dark;
214
+ export type SpacingToken = keyof typeof spacing;
215
+ export type RadiusToken = keyof typeof radius;
216
+ export type FontSize = keyof typeof font.size;
217
+ export type FontWeight = keyof typeof font.weight;
@@ -0,0 +1,21 @@
1
+ # Component usage guides
2
+
3
+ Published `@naeil/ui@0.3.0` (2026-10-03; [release](https://github.com/naeil-dev/naeil-ui/releases/tag/v0.3.0)). [Setup, themes, fonts and shared responsibilities](common.md). Open the [live Storybook](https://naeil-dev.github.io/naeil-ui/) or run `pnpm storybook` and choose each family’s Docs or Usage entry. Select chooses a value; DropdownMenu chooses an action.
4
+
5
+ | Family | Guide | Runnable story |
6
+ | --- | --- | --- |
7
+ | Button | [Button](./button.md) | UI / Button / Usage |
8
+ | Input | [Input](./input.md) | UI / Input / Usage |
9
+ | Card | [Card](./card.md) | UI / Card / Usage |
10
+ | Dialog | [Dialog](./dialog.md) | UI / Dialog / Usage |
11
+ | DropdownMenu | [DropdownMenu](./dropdown-menu.md) | UI / DropdownMenu / Usage |
12
+ | Badge | [Badge](./badge.md) | UI / Badge / Usage |
13
+ | Avatar | [Avatar](./avatar.md) | UI / Avatar / Usage |
14
+ | Toaster | [Toaster](./sonner.md) | UI / Toaster / Usage |
15
+ | Select | [Select](./select.md) | UI / Select / Usage |
16
+ | Switch | [Switch](./switch.md) | UI / Switch / Usage |
17
+ | Checkbox | [Checkbox](./checkbox.md) | UI / Checkbox / Usage |
18
+ | Tabs | [Tabs](./tabs.md) | UI / Tabs / Usage |
19
+ | Textarea | [Textarea](./textarea.md) | UI / Textarea / Usage |
20
+
21
+ [Field, native radio, Select and action composition](./composition.md) explains consumer state and semantic choices. [Demand decision](../design/component-demand.md) records why these two families were added.
@@ -0,0 +1,47 @@
1
+ # Avatar
2
+
3
+ Use Avatar for a person/team identity image with a fallback. Prefer plain names when imagery adds no value; put actions on a named Button/link rather than on the Avatar span.
4
+
5
+ ## API and wrapper defaults
6
+
7
+ Exports: `Avatar`, `AvatarImage`, `AvatarFallback`, `AvatarBadge`, `AvatarGroup`, `AvatarGroupCount`.
8
+
9
+ Avatar forwards Radix Root span props and defaults **size="default"**; sizes `sm`/`default`/`lg` render 24/32/40px graphics. Image forwards native image props and `onLoadingStatusChange`. Fallback accepts `delayMs`; wrapper sets no delay default. Badge is a span; Group/GroupCount are divs. These have styling, not automatic presence, count, tooltip or click logic.
10
+
11
+ Inherited [Avatar 1.1.10 declaration](https://unpkg.com/@radix-ui/react-avatar@1.1.10/dist/index.d.ts).
12
+
13
+ The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/81fd395031dc5b20074c4d915a8585360ce2d28a/src/components/ui/avatar.tsx) is the API authority.
14
+
15
+ ## Composition
16
+
17
+ ```tsx
18
+ <div className="flex items-center gap-3">
19
+ <Avatar><AvatarImage src={person.photo} alt="" />
20
+ <AvatarFallback aria-hidden="true">AL</AvatarFallback></Avatar>
21
+ <span>{person.name}</span>
22
+ </div>
23
+ ```
24
+
25
+ Import the named components from `@naeil/ui/ui`. Variables/handlers in snippets are consumer-owned. Run **UI / Avatar / Usage** in Storybook for a complete interactive example; its Docs page renders this same guide.
26
+
27
+ ## Keyboard, focus and accessible content
28
+
29
+ Use alt="" and hide redundant initials when an adjacent name conveys the identity; otherwise provide meaningful alt and a labeled fallback (e.g. role="img" aria-label="Alex Lee"). Avatar itself is not a button/tab stop. Presence markers/counts need visible or accessible text; color alone is insufficient.
30
+
31
+ For a named group, supply `role="group"` with `aria-label` on AvatarGroup. AvatarGroupCount is a generic div: convey the count with actual text rather than `aria-label`. For example, hide the visual shorthand and provide the full meaning as screen-reader text:
32
+
33
+ ```tsx
34
+ <AvatarGroup role="group" aria-label="Team members">
35
+ {/* Consumer-owned member avatars */}
36
+ <AvatarGroupCount>
37
+ <span aria-hidden="true">+3</span>
38
+ <span className="sr-only">3 additional members</span>
39
+ </AvatarGroupCount>
40
+ </AvatarGroup>
41
+ ```
42
+
43
+ ## States, resilience and mistakes
44
+
45
+ Loading or failed image falls back; demonstrate missing/error/loaded images. Empty identity needs a consumer placeholder, not fabricated initials. Disabled does not apply to the graphic. GroupCount is supplied by consumers and does not compute hidden members. Long Korean/English/Japanese names belong beside the fixed graphic and wrap; keep meaningful counts accessible and do not turn tiny avatar imagery into an undersized target.
46
+
47
+ Check this example in light/dark, comfortable/compact and mobile, including long Korean, English and Japanese text. Consumer overrides remain supported; follow the [shared override and acceptance guidance](common.md#styling-themes-and-overrides). Actual engine results and manual limits are in the [support matrix](../design/public-ui-support.md).
@@ -0,0 +1,29 @@
1
+ # Badge
2
+
3
+ Use Badge for concise status/category text. Prefer readable body/helper text for essential instructions, Button for actions, and a real link for navigation.
4
+
5
+ ## API and wrapper defaults
6
+
7
+ Exports: `Badge`, `badgeVariants`. Native span props plus `variant` and `asChild`. Defaults: **variant="default", asChild=false**. Variants: `default`, `secondary`, `outline`, `destructive`, `success`, `warning`, `error`, `info`. asChild uses Slot.Root; no disabled/loading or announcement API. Default text is intentionally small and single-line.
8
+
9
+ The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/81fd395031dc5b20074c4d915a8585360ce2d28a/src/components/ui/badge.tsx) is the API authority.
10
+
11
+ ## Composition
12
+
13
+ ```tsx
14
+ <Badge variant="success">Complete</Badge>
15
+ <Badge variant="error">Save failed</Badge>
16
+ <Badge asChild variant="outline"><a href="/activity">Activity</a></Badge>
17
+ ```
18
+
19
+ Import the named components from `@naeil/ui/ui`. Variables/handlers in snippets are consumer-owned. Run **UI / Badge / Usage** in Storybook for a complete interactive example; its Docs page renders this same guide.
20
+
21
+ ## Keyboard, focus and accessible content
22
+
23
+ Static badges are not focusable. Name status by text, not only color/icon. Dynamic important status needs an appropriate surrounding status/alert region; Badge alone does not announce changes. asChild links use native keyboard behavior; an icon-only badge needs an accessible text equivalent.
24
+
25
+ ## States, resilience and mistakes
26
+
27
+ Pending/empty/error are literal status content; disabled is inapplicable to a static badge. Avoid essential long text in tiny tags; use a paragraph instead, or deliberate `whitespace-normal break-words max-w-full` overrides and review. Do not infer interactivity from hover or pass disabled to a span/link.
28
+
29
+ Check this example in light/dark, comfortable/compact and mobile, including long Korean, English and Japanese text. Consumer overrides remain supported; follow the [shared override and acceptance guidance](common.md#styling-themes-and-overrides). Actual engine results and manual limits are in the [support matrix](../design/public-ui-support.md).
@@ -0,0 +1,39 @@
1
+ # Button
2
+
3
+ Use Button for an action, a native anchor for navigation, and Select for a value. Prefer one clear primary action per task.
4
+
5
+ ## API and wrapper defaults
6
+
7
+ Exports: `Button`, `buttonVariants`.
8
+
9
+ | Prop | Contract / wrapper default |
10
+ | --- | --- |
11
+ | `variant` | `default` (default), `destructive`, `outline`, `secondary`, `ghost`, `link` |
12
+ | `size` | `default` (default), `xs`, `sm`, `lg`, `icon`, `icon-xs`, `icon-sm`, `icon-lg` |
13
+ | `asChild` | `false`; Slot.Root composes one supplied element |
14
+ | Native props | `type`, `disabled`, `onClick`, `name`, `value`, form and ARIA props; no wrapper `type` default |
15
+
16
+ `size` names are preserved; shared CSS controls actual height (40/36px; lg adds 4px; mobile minimum 44px). `xs` does not imply an extra-small touch target. `loading` is not an API.
17
+
18
+ The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/81fd395031dc5b20074c4d915a8585360ce2d28a/src/components/ui/button.tsx) is the API authority.
19
+
20
+ ## Composition
21
+
22
+ ```tsx
23
+ <Button type="button" disabled={saving} aria-busy={saving}
24
+ onClick={save}>{saving ? 'Saving…' : 'Save settings'}</Button>
25
+ <p role="status">{result}</p>
26
+ <Button asChild variant="link"><a href="/settings">Settings</a></Button>
27
+ ```
28
+
29
+ Import the named components from `@naeil/ui/ui`. Variables/handlers in snippets are consumer-owned. Run **UI / Button / Usage** in Storybook for a complete interactive example; its Docs page renders this same guide.
30
+
31
+ ## Keyboard, focus and accessible content
32
+
33
+ Native buttons activate with Enter/Space and show focus-visible; disabled buttons leave the tab sequence. Name icon buttons with aria-label. Keep loading text meaningful and announce completion/error near the form; restore enabled state after failure. A button without type inside a form submits by browser default.
34
+
35
+ ## States, resilience and mistakes
36
+
37
+ Demonstrate normal, disabled, saving and recovery; no-data/error belong to the operation. Do not nest Button in a link or treat variant="link" as navigation. With asChild anchors, own href/activation/disabled policy. For long translated actions use `className="h-auto min-h-11 whitespace-normal py-2"` and check both densities.
38
+
39
+ Check this example in light/dark, comfortable/compact and mobile, including long Korean, English and Japanese text. Consumer overrides remain supported; follow the [shared override and acceptance guidance](common.md#styling-themes-and-overrides). Actual engine results and manual limits are in the [support matrix](../design/public-ui-support.md).
@@ -0,0 +1,32 @@
1
+ # Card
2
+
3
+ Use Card to group related information or a form. Use plain sections/headings for content that needs no surface, Dialog for a modal task. CardElevated expresses an elevated surface; neither is an interactive control.
4
+
5
+ ## API and wrapper defaults
6
+
7
+ Exports: `Card`, `CardElevated`, `CardHeader`, `CardFooter`, `CardTitle`, `CardAction`, `CardDescription`, `CardContent`. All accept `React.ComponentProps<"div">`; no variant/size/asChild/loading API. Card and CardElevated use `data-slot="card"`, default panel spacing and consumer className; CardElevated adds shadow/elevated dark surface. CardTitle is a **div**, not a semantic heading. CardAction occupies the second header column.
8
+
9
+ The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/81fd395031dc5b20074c4d915a8585360ce2d28a/src/components/ui/card.tsx) is the API authority.
10
+
11
+ ## Composition
12
+
13
+ ```tsx
14
+ <Card>
15
+ <CardHeader><CardTitle><h2>Profile</h2></CardTitle>
16
+ <CardDescription>Public account details.</CardDescription></CardHeader>
17
+ <CardContent><p>No profile description yet.</p></CardContent>
18
+ <CardFooter><Button type="button">Edit profile</Button></CardFooter>
19
+ </Card>
20
+ ```
21
+
22
+ Import the named components from `@naeil/ui/ui`. Variables/handlers in snippets are consumer-owned. Run **UI / Card / Usage** in Storybook for a complete interactive example; its Docs page renders this same guide.
23
+
24
+ ## Keyboard, focus and accessible content
25
+
26
+ Static cards are not tab stops. Put real buttons/links inside and a semantic heading inside CardTitle. A region label is the consumer’s choice; do not add button role/tabIndex to a card containing other controls.
27
+
28
+ ## States, resilience and mistakes
29
+
30
+ Loading/empty/error are consumer content with status/retry actions, not Card props; disabled is inapplicable to the static surface. Keep CardAction concise, wrap long localized headings and use `w-full max-w-*` instead of fixed widths. Avoid nested cards and implicit clickable-card behavior; an entire link card must contain no nested controls.
31
+
32
+ Check this example in light/dark, comfortable/compact and mobile, including long Korean, English and Japanese text. Consumer overrides remain supported; follow the [shared override and acceptance guidance](common.md#styling-themes-and-overrides). Actual engine results and manual limits are in the [support matrix](../design/public-ui-support.md).
@@ -0,0 +1,35 @@
1
+ # Checkbox
2
+
3
+ Use Checkbox for independent selections, consent or a mixed group-selection state. Use Switch for an on/off preference and Select for one mutually exclusive value.
4
+
5
+ ## API and wrapper defaults
6
+
7
+ Export: `Checkbox`. Forwards Radix Root props: `checked`/`defaultChecked`/`onCheckedChange` with **boolean | "indeterminate"**, `disabled`, `required`, `name`, `value`, `form`, `id`, button/ARIA props. Wrapper sets no checked default and renders its own Indicator (check/minus icons); no public Indicator, size or loading API. Graphic is 18×18px with a 44px pseudo-element hit area. The consumer computes mixed state from child selections.
8
+
9
+ Inherited [Checkbox 1.3.3 declaration](https://unpkg.com/@radix-ui/react-checkbox@1.3.3/dist/index.d.ts).
10
+
11
+ The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/81fd395031dc5b20074c4d915a8585360ce2d28a/src/components/ui/checkbox.tsx) is the API authority.
12
+
13
+ ## Composition
14
+
15
+ ```tsx
16
+ <label className="ui-choice-label">
17
+ <Checkbox name="notifications" value="email" />Email notifications
18
+ </label>
19
+ <Checkbox checked={selectedCount === total ? true : selectedCount ? 'indeterminate' : false}
20
+ onCheckedChange={selectAll} aria-label="Select all items" />
21
+ ```
22
+
23
+ Import the named components from `@naeil/ui/ui`. Variables/handlers in snippets are consumer-owned. Run **UI / Checkbox / Usage** in Storybook for a complete interactive example; its Docs page renders this same guide.
24
+
25
+ The additional **Required consent** Storybook example demonstrates a consumer-owned required error, focus, pending confirmation and recovery using the same public props.
26
+
27
+ ## Keyboard, focus and accessible content
28
+
29
+ Tab focuses enabled checkboxes; Space toggles. Mixed state exposes aria-checked="mixed" and a minus glyph; onCheckedChange receives boolean/mixed, so handle it intentionally (checked === true when coercing). Checked values submit; unchecked, indeterminate and disabled values do not represent selected items in FormData. Label consent clearly; use fieldset/legend for a related group.
30
+
31
+ ## States, resilience and mistakes
32
+
33
+ Demonstrate unchecked/checked/mixed/disabled and required error/recovery. Loading/error/empty are group or operation state, not Checkbox props. Keep descriptive errors visible and connected; do not replace a disabled checkbox with unlabeled decoration. Use 44px label rows/gaps, wrap long Korean/English/Japanese labels, and verify indicators remain visible with forced colors. Do not invent an automatic select-all relationship.
34
+
35
+ Check this example in light/dark, comfortable/compact and mobile, including long Korean, English and Japanese text. Consumer overrides remain supported; follow the [shared override and acceptance guidance](common.md#styling-themes-and-overrides). Actual engine results and manual limits are in the [support matrix](../design/public-ui-support.md).
@@ -0,0 +1,31 @@
1
+ # Shared usage contract
2
+
3
+ These guides describe **@naeil/ui 0.3.0, published 2026-10-03** ([release](https://github.com/naeil-dev/naeil-ui/releases/tag/v0.3.0)); [live Docs and Usage examples](https://naeil-dev.github.io/naeil-ui/) are available. Start with the [consumer setup and migration](../design/v2-migration.md). All listed exports are available from `@naeil/ui/ui`; the legacy root needs the optional Next/next-intl peers. Examples import real wrappers; product state, validation and network requests belong to consumers.
4
+
5
+ ## Styling, themes and overrides
6
+
7
+ Import `@naeil/ui/globals.css` once and register `@source` for the installed package's `dist` in your Tailwind 4 build. Alternatively load both theme.css and components.css alongside your existing Tailwind setup. Deliver Pretendard and, for Japanese, Noto Sans JP locally through your app's font pipeline; the package does not fetch fonts. Keep font OFL notices with redistributed binaries. The independent React example demonstrates consumer-owned delivery.
8
+
9
+ Use `.light`/`.dark` on the document root to override OS theme. Root `data-ui-density="compact"` changes spacing, not text size: default controls are 40px, compact 36px, small screens/coarse pointers at least 44px. Portals use document-level styles/density, so a locally themed subtree alone does not theme portaled content. Purpose-specific reading/settings/list widths are 640/640/1200px caps that shrink to available space; the product owns its application frame.
10
+
11
+ `className` is merged by `cn` (clsx + tailwind-merge). Consumer classes and primitive props remain available. Recheck specificity, text/control/focus contrast, mobile targets and portaled content after overriding. Override semantic color companions together; do not replace only an action hue or remove focus outlines. Reduced-motion rules remove shared transitions/animations. Custom consumer animation still needs its own reduced-motion treatment.
12
+
13
+ ## Semantics and state ownership
14
+
15
+ Give every input/action a visible accessible name. Connect native labels with IDs, helper/error text with `aria-describedby`, invalid controls with `aria-invalid`, and async outcomes with an appropriate status/alert region. Placeholder is not a label. Preserve input on failure, give a recovery action, and enforce server validation in your application.
16
+
17
+ Loading is composition: state + meaningful text + `aria-busy`, with `disabled` when repeating the operation is invalid. Button has no `loading` prop. Static Card/Badge/Avatar do not gain loading/disabled/error behavior merely from an ARIA attribute. Use meaningful content and surrounding status messages. Native links are not disabled by a `disabled` prop forwarded through `asChild`.
18
+
19
+ Avoid nested buttons/links or embedding unrelated controls in a single clickable surface. A small Switch/Checkbox graphic has a 44px pseudo-element target; keep label rows at least 44px and leave room so adjacent targets do not overlap. Test actual pointer hit areas, keyboard focus and text wrapping in the consumer.
20
+
21
+ ## Long and localized content
22
+
23
+ Examples include Korean, English and Japanese. Set `lang` on translated content (Japanese selects Noto Sans JP in shared globals). Use responsive widths, `min-width: 0`, flexible heights and consumer wrapping classes for long actions/status text. The wrappers may use single-line truncation; retain the complete accessible name and offer the full value elsewhere if necessary. Do not shrink essential text to fit.
24
+
25
+ Run each affected composition under matching light/dark, comfortable/compact, mobile and long-content conditions. Check 320 CSS-pixel reflow, text scaling, CSS zoom and actual native browser zoom separately. Axe and keyboard automation do not replace assistive-technology use. Follow the [manual acceptance and support matrix](../design/public-ui-support.md) and [dated verification](../design/public-ui-stage3-verification.md).
26
+
27
+ [Field/choice composition](./composition.md) gives complete label/error and native-radio guidance. [Tabs](./tabs.md) and [Textarea](./textarea.md) extend the same shared contract; current extension results are in [Stage 4 verification](../design/public-ui-stage4-verification.md).
28
+
29
+ ## Exact inherited contracts
30
+
31
+ Native wrappers use React 19 element props: inspect your installed `@types/react/index.d.ts`, or infer `React.ComponentProps<typeof Component>`. Radix wrappers use `React.ComponentProps<typeof Primitive.Part>`; inspect the installed declarations reached through `radix-ui/dist/index.d.ts`. Per-family guides link pinned declarations inspected locally during this work. Those links describe the inherited contract, not a promise that all primitive configurations have been tested. Wrapper defaults below are separate from primitive defaults; omitted primitive defaults are not invented here. Consumer examples and browser checks demonstrate only their recorded configuration.
@@ -0,0 +1,68 @@
1
+ # Fields and choice composition
2
+
3
+ The screen owns form state, validation, IDs and asynchronous work. `@naeil/ui` adds no FormField wrapper, form-state context or RadioGroup API. Existing HTML plus shared controls covers the observed common contract; see the [demand decision](../design/component-demand.md).
4
+
5
+ ## Label, help, error and recovery
6
+
7
+ ```tsx
8
+ import { useId, useRef, useState } from 'react';
9
+ import { Button, Textarea } from '@naeil/ui/ui';
10
+
11
+ function NotesForm() {
12
+ const id = useId();
13
+ const field = useRef<HTMLTextAreaElement>(null);
14
+ const [invalid, setInvalid] = useState(false);
15
+ const [status, setStatus] = useState('');
16
+ return <form noValidate onSubmit={event => {
17
+ event.preventDefault();
18
+ const valid = field.current!.validity.valid;
19
+ setInvalid(!valid);
20
+ if (!valid) { field.current!.focus(); return; }
21
+ // Application-owned persistence and server validation go here.
22
+ setStatus('Notes accepted for this example.');
23
+ }} className="grid gap-6">
24
+ <div className="grid gap-2">
25
+ <label htmlFor={id}>Notes</label>
26
+ <Textarea ref={field} id={id} name="notes" required
27
+ aria-invalid={invalid} aria-describedby={`${id}-help ${id}-error`}
28
+ onChange={() => { setInvalid(false); setStatus(''); }} />
29
+ <p id={`${id}-help`}>Enter a description. Enter starts a new line.</p>
30
+ <p id={`${id}-error`} role="alert">{invalid ? 'Enter notes before saving.' : ''}</p>
31
+ </div>
32
+ <Button type="submit">Save notes</Button>
33
+ <p role="status">{status}</p>
34
+ </form>;
35
+ }
36
+ ```
37
+
38
+ Use the same label/help/error relationship with [Input](./input.md); put it on [SelectTrigger](./select.md) for Select. Keep existing description IDs when appending an error ID. Do not use placeholder as a name or nest multiple controls inside one label. `useId` avoids collisions when a composition repeats. Announce persistent errors once and preserve drafts; disable repeated submission with meaningful busy text when necessary. Disabled fields are omitted from FormData; read-only values remain included. HTML client validation is not server validation.
39
+
40
+ ## Choose values with native radios or Select
41
+
42
+ For a small visible set of mutually exclusive values, use a `fieldset`/`legend` and native radios sharing one `name`. The label row provides the 44px target; leave space between rows. Native checked state, arrow navigation, form values and required validation remain native.
43
+
44
+ ```tsx
45
+ <fieldset>
46
+ <legend>Delivery channel</legend>
47
+ <label className="ui-choice-label">
48
+ <input className="ui-focus accent-primary" type="radio"
49
+ name="delivery" value="instant" defaultChecked required />
50
+ Instant delivery
51
+ </label>
52
+ <label className="ui-choice-label">
53
+ <input className="ui-focus accent-primary" type="radio"
54
+ name="delivery" value="digest" />
55
+ Daily digest
56
+ </label>
57
+ </fieldset>
58
+ ```
59
+
60
+ Use a unique group name for separate forms/groups, or controlled `checked`/`onChange` when the product owns state. Connect group help/errors through `aria-describedby` and associate field-level errors where needed. A card selection still needs a value-input contract; visual selection or `aria-pressed` alone does not create radio semantics.
61
+
62
+ Use [Select](./select.md) for a compact finite value input, for example a language, and preserve its `name` for form submission. Checkbox means independent boolean choices; Switch means an on/off preference. Do not use DropdownMenu action-radio items as the form's ordinary value input.
63
+
64
+ ## Actions, toggle actions and panels
65
+
66
+ A Button performs an action such as clearing a draft. A toggle action such as pinning a preview uses a stable visible label and `aria-pressed`; it is not automatically a mutually exclusive form option. DropdownMenu groups commands. Tabs switches related panels; links navigate routes. These choices depend on the task, not the shape of the control.
67
+
68
+ Run **UI / Textarea / Usage** for the complete field/radio/Select/action/toggle specimen and **UI / Tabs / Usage** for panel state/mount responsibility. Both use actual public controls. Apply the [shared contract](common.md) and preserve the product's application frame outside purpose-specific settings/reading/list widths. Check keyboard, native FormData, errors, long text, themes/density and overrides. [Manual acceptance](../design/public-ui-support.md) remains separate from automated checks.
@@ -0,0 +1,41 @@
1
+ # Dialog
2
+
3
+ Use Dialog for a focused modal task or detail that requires an interruption. Prefer an inline panel for ordinary editing and a native confirmation flow suited to destructive actions; this package does not export AlertDialog.
4
+
5
+ ## API and wrapper defaults
6
+
7
+ Exports: `Dialog`, `DialogTrigger`, `DialogContent`, `DialogHeader`, `DialogFooter`, `DialogTitle`, `DialogDescription`, `DialogClose`, `DialogPortal`, `DialogOverlay`.
8
+
9
+ Root forwards Radix `open`/`defaultOpen`/`onOpenChange`/`modal`; wrapper adds no root defaults. Trigger/Close accept primitive button props including `asChild`. Content forwards primitive focus/outside/Escape handlers and **defaults `showCloseButton=true`**; it creates its own Portal and Overlay. Footer is a div with **`showCloseButton=false`**. Header is a div; Title/Description wrap primitive semantic elements. Built-in close labels are English `Close`. For localized close controls set Content showCloseButton=false and compose DialogClose. Content does not expose a portal `container` prop; the separately exported DialogPortal is for a custom composition.
10
+
11
+ Inherited [Dialog 1.1.15 declaration](https://unpkg.com/@radix-ui/react-dialog@1.1.15/dist/index.d.ts).
12
+
13
+ The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/81fd395031dc5b20074c4d915a8585360ce2d28a/src/components/ui/dialog.tsx) is the API authority.
14
+
15
+ ## Composition
16
+
17
+ ```tsx
18
+ <Dialog>
19
+ <DialogTrigger asChild><Button type="button">Edit name</Button></DialogTrigger>
20
+ <DialogContent showCloseButton={false}>
21
+ <DialogHeader><DialogTitle>Edit name</DialogTitle>
22
+ <DialogDescription>Change your display name.</DialogDescription></DialogHeader>
23
+ <label htmlFor="name">Name</label><Input id="name" />
24
+ <DialogFooter><DialogClose asChild>
25
+ <Button type="button" variant="outline">Cancel</Button>
26
+ </DialogClose></DialogFooter>
27
+ </DialogContent>
28
+ </Dialog>
29
+ ```
30
+
31
+ Import the named components from `@naeil/ui/ui`. Variables/handlers in snippets are consumer-owned. Run **UI / Dialog / Usage** in Storybook for a complete interactive example; its Docs page renders this same guide.
32
+
33
+ ## Keyboard, focus and accessible content
34
+
35
+ For the modal example, opening moves focus inside, Tab/Shift+Tab stay inside, Escape dismisses and focus returns to Trigger. Preserve default focus handlers unless you supply an equivalent appropriate destination. Always provide DialogTitle and a relevant Description; use aria-describedby={undefined} only when intentionally omitting a description. A triggerless controlled dialog requires a consumer focus-return destination.
36
+
37
+ ## States, resilience and mistakes
38
+
39
+ Disabled applies to triggers/actions; loading/error/empty are content state. Keep failed input inside the dialog and enable retry. Long/mobile content should remain scrollable within the viewport (e.g. max-h-[calc(100%-2rem)] overflow-y-auto); do not hide all close actions. Consumer outside-dismissal overrides need keyboard/focus review. Do not nest modal roots casually or prevent Escape without an accessible exit.
40
+
41
+ Check this example in light/dark, comfortable/compact and mobile, including long Korean, English and Japanese text. Consumer overrides remain supported; follow the [shared override and acceptance guidance](common.md#styling-themes-and-overrides). Actual engine results and manual limits are in the [support matrix](../design/public-ui-support.md).
@@ -0,0 +1,38 @@
1
+ # DropdownMenu
2
+
3
+ Use DropdownMenu for actions and view toggles, Select for a form value, and navigation links for destinations. Menu checkbox/radio items are action-menu options, not a substitute for form inputs.
4
+
5
+ ## API and wrapper defaults
6
+
7
+ Exports: `DropdownMenu`, `DropdownMenuPortal`, `DropdownMenuTrigger`, `DropdownMenuContent`, `DropdownMenuGroup`, `DropdownMenuLabel`, `DropdownMenuItem`, `DropdownMenuCheckboxItem`, `DropdownMenuRadioGroup`, `DropdownMenuRadioItem`, `DropdownMenuSeparator`, `DropdownMenuShortcut`, `DropdownMenuSub`, `DropdownMenuSubTrigger`, `DropdownMenuSubContent`.
8
+
9
+ Root: primitive `open`/`defaultOpen`/`onOpenChange`, `modal`, `dir`; no wrapper root defaults. Trigger supports `asChild` and `disabled`. Content creates Portal and defaults `sideOffset=4`; forwards align/side/collision/focus/outside handlers. Item defaults `variant="default"`, accepts `destructive` and `inset`; uses `onSelect`, `disabled`, `textValue`. Label/SubTrigger accept `inset`. CheckboxItem accepts boolean/indeterminate `checked` and `onCheckedChange`; RadioGroup accepts value/onValueChange, RadioItem requires value. Sub forwards open state; SubContent does not create a Portal automatically. Shortcut is a styled span and does **not** register a keyboard shortcut.
10
+
11
+ Inherited [DropdownMenu 2.1.16 declaration](https://unpkg.com/@radix-ui/react-dropdown-menu@2.1.16/dist/index.d.ts), including its inherited Menu types.
12
+
13
+ The [shared usage contract](common.md) covers setup, inherited/native prop typing, themes, density, font delivery and overrides. [Wrapper source](https://github.com/naeil-dev/naeil-ui/blob/81fd395031dc5b20074c4d915a8585360ce2d28a/src/components/ui/dropdown-menu.tsx) is the API authority.
14
+
15
+ ## Composition
16
+
17
+ ```tsx
18
+ <DropdownMenu>
19
+ <DropdownMenuTrigger asChild><Button type="button" variant="outline">Actions</Button></DropdownMenuTrigger>
20
+ <DropdownMenuContent>
21
+ <DropdownMenuItem onSelect={copy}>Copy reference</DropdownMenuItem>
22
+ <DropdownMenuItem disabled>Archive (unavailable)</DropdownMenuItem>
23
+ <DropdownMenuItem variant="destructive" onSelect={confirmDelete}>Delete…</DropdownMenuItem>
24
+ </DropdownMenuContent>
25
+ </DropdownMenu>
26
+ ```
27
+
28
+ Import the named components from `@naeil/ui/ui`. Variables/handlers in snippets are consumer-owned. Run **UI / DropdownMenu / Usage** in Storybook for a complete interactive example; its Docs page renders this same guide.
29
+
30
+ ## Keyboard, focus and accessible content
31
+
32
+ Enter/Space/ArrowDown open a trigger; arrows/Home/End/typeahead navigate enabled items, Enter selects, Escape closes and returns focus in the tested composition. Submenus use ArrowRight/ArrowLeft for LTR. Name icon-only triggers. Use onSelect for keyboard and pointer activation; announce operation success/failure outside the transient menu.
33
+
34
+ ## States, resilience and mistakes
35
+
36
+ Disabled items remain descriptive but cannot select. Loading/empty/error belong to action state or an external status region; disable unavailable actions and supply recovery outside. Do not nest buttons inside menu items. Long localized actions must wrap within a viewport-bounded Content; pass className="max-w-[calc(100vw-2rem)]" and wrapping styles as needed. Do not imply shortcuts work unless the consumer registers them.
37
+
38
+ Check this example in light/dark, comfortable/compact and mobile, including long Korean, English and Japanese text. Consumer overrides remain supported; follow the [shared override and acceptance guidance](common.md#styling-themes-and-overrides). Actual engine results and manual limits are in the [support matrix](../design/public-ui-support.md).