Skip to content

Commit 0003bf5

Browse files
Add red border in error state to Combobox, Select, Autocomplete, and SensitiveInput (#528)
* fix: add red border in error state to Combobox, Select, Autocomplete, and SensitiveInput * docs: split Select description and error demos into separate examples
1 parent 0e79214 commit 0003bf5

12 files changed

Lines changed: 496 additions & 86 deletions

File tree

.changeset/red-borders-fix.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@cloudflare/kumo": patch
3+
---
4+
5+
Fix error state red border on Combobox, Select, Autocomplete, and SensitiveInput to match Input behavior

packages/kumo-docs-astro/src/components/demos/SelectDemo.tsx

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,14 @@ export function SelectWithoutLabelDemo() {
7979
);
8080
}
8181

82-
/** Select with label, description, and error handling. */
83-
export function SelectWithFieldDemo() {
82+
/** Select with label and description text. */
83+
export function SelectWithDescriptionDemo() {
8484
const [value, setValue] = useState<string | null>(null);
8585

8686
return (
8787
<Select
8888
label="Issue Type"
8989
description="Choose the category that best describes your issue"
90-
error={!value ? "Please select an issue type" : undefined}
9190
className="w-[280px]"
9291
value={value}
9392
onValueChange={(v) => setValue(v as string | null)}
@@ -100,6 +99,23 @@ export function SelectWithFieldDemo() {
10099
);
101100
}
102101

102+
/** Select with label and validation error. */
103+
export function SelectWithErrorDemo() {
104+
return (
105+
<Select
106+
label="Issue Type"
107+
error="Please select an issue type"
108+
className="w-[280px]"
109+
value={null}
110+
items={{
111+
bug: "Bug",
112+
documentation: "Documentation",
113+
feature: "Feature",
114+
}}
115+
/>
116+
);
117+
}
118+
103119
/** Select with placeholder text when no value is selected. */
104120
export function SelectPlaceholderDemo() {
105121
const [value, setValue] = useState<string | null>(null);

packages/kumo-docs-astro/src/components/demos/SelectStressTestDemo.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,10 @@ export function SelectGroupedOptionsDemo() {
345345
// ============================================================================
346346

347347
/**
348-
* Tests: Field integration with error and description.
348+
* Tests: Field integration with error and description together.
349+
* The Field component treats description and error as mutually exclusive —
350+
* when an error is present, it replaces the description in the UI.
351+
* This stress test verifies that toggling between the two states works correctly.
349352
*/
350353
export function SelectFieldIntegrationDemo() {
351354
const [value, setValue] = useState<string | null>(null);

packages/kumo-docs-astro/src/pages/components/select.mdx

Lines changed: 105 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ import {
1414
SelectBasicDemo,
1515
SelectSizesDemo,
1616
SelectWithoutLabelDemo,
17-
SelectWithFieldDemo,
17+
SelectWithDescriptionDemo,
18+
SelectWithErrorDemo,
1819
SelectPlaceholderDemo,
1920
SelectWithTooltipDemo,
2021
SelectCustomRenderingDemo,
@@ -45,7 +46,7 @@ import {
4546

4647
### Barrel
4748

48-
<CodeBlock code={`import { Select } from "@cloudflare/kumo";`} lang="tsx" />
49+
<CodeBlock code={`import { Select } from "@cloudflare/kumo";`} lang="tsx" />
4950

5051
### Granular
5152

@@ -106,10 +107,10 @@ export default function Example() {
106107

107108
### Sizes
108109

109-
<p>
110-
Use the <code class="rounded bg-kumo-control px-1 py-0.5 text-sm">size</code> prop
111-
to match Input sizing (xs, sm, base, lg).
112-
</p>
110+
<p>
111+
Use the <code class="rounded bg-kumo-control px-1 py-0.5 text-sm">size</code>{" "}
112+
prop to match Input sizing (xs, sm, base, lg).
113+
</p>
113114

114115
<ComponentExample demo="SelectSizesDemo">
115116
<SelectSizesDemo client:load />
@@ -122,28 +123,47 @@ export default function Example() {
122123

123124
### Without Visible Label
124125

125-
<p>
126-
When a visible label isn't needed (e.g., in compact UIs or when context is clear),
127-
use `aria-label` for accessibility.
128-
</p>
126+
<p>
127+
When a visible label isn't needed (e.g., in compact UIs or when context is
128+
clear), use `aria-label` for accessibility.
129+
</p>
129130

130131
<ComponentExample demo="SelectWithoutLabelDemo">
131132
<SelectWithoutLabelDemo client:load />
132133
</ComponentExample>
133134
</ComponentSection>
134135

135-
{/* With Description and Error */}
136+
{/* With Description */}
136137

137138
<ComponentSection>
138139

139-
### With Description and Error
140+
### With Description
140141

141-
<p>
142-
Select integrates with the Field wrapper to show description text and validation errors.
143-
</p>
142+
<p>
143+
Select integrates with the Field wrapper to show description text below the
144+
input.
145+
</p>
144146

145-
<ComponentExample demo="SelectWithFieldDemo">
146-
<SelectWithFieldDemo client:load />
147+
<ComponentExample demo="SelectWithDescriptionDemo">
148+
<SelectWithDescriptionDemo client:load />
149+
</ComponentExample>
150+
</ComponentSection>
151+
152+
{/* With Error */}
153+
154+
<ComponentSection>
155+
156+
### With Error
157+
158+
<p>
159+
Pass the{" "}
160+
<code class="rounded bg-kumo-control px-1 py-0.5 text-sm">error</code> prop to
161+
display a validation error. When an error is present, it replaces the
162+
description in the UI.
163+
</p>
164+
165+
<ComponentExample demo="SelectWithErrorDemo">
166+
<SelectWithErrorDemo client:load />
147167
</ComponentExample>
148168
</ComponentSection>
149169

@@ -153,12 +173,17 @@ export default function Example() {
153173

154174
### Placeholder
155175

156-
<p>
157-
Use the <code class="rounded bg-kumo-control px-1 py-0.5 text-sm">placeholder</code> prop
158-
to show text when no value is selected. When using <code class="rounded bg-kumo-control px-1 py-0.5 text-sm">renderValue</code> to
159-
customize the display of selected values, the placeholder is shown instead of
160-
calling <code class="rounded bg-kumo-control px-1 py-0.5 text-sm">renderValue</code> when the value is <code class="rounded bg-kumo-control px-1 py-0.5 text-sm">null</code>.
161-
</p>
176+
<p>
177+
Use the{" "}
178+
<code class="rounded bg-kumo-control px-1 py-0.5 text-sm">placeholder</code>{" "}
179+
prop to show text when no value is selected. When using{" "}
180+
<code class="rounded bg-kumo-control px-1 py-0.5 text-sm">renderValue</code>{" "}
181+
to customize the display of selected values, the placeholder is shown instead
182+
of calling{" "}
183+
<code class="rounded bg-kumo-control px-1 py-0.5 text-sm">renderValue</code>{" "}
184+
when the value is{" "}
185+
<code class="rounded bg-kumo-control px-1 py-0.5 text-sm">null</code>.
186+
</p>
162187

163188
```tsx
164189
<Select
@@ -179,10 +204,10 @@ export default function Example() {
179204

180205
### Label with Tooltip
181206

182-
<p>
183-
Add a tooltip icon next to the label for additional context using <code
184-
class="rounded bg-kumo-control px-1 py-0.5 text-sm">labelTooltip</code>.
185-
</p>
207+
<p>
208+
Add a tooltip icon next to the label for additional context using{" "}
209+
<code class="rounded bg-kumo-control px-1 py-0.5 text-sm">labelTooltip</code>.
210+
</p>
186211

187212
<ComponentExample demo="SelectWithTooltipDemo">
188213
<SelectWithTooltipDemo client:load />
@@ -195,11 +220,13 @@ export default function Example() {
195220

196221
### Custom Rendering
197222

198-
<p>
199-
Use <code class="rounded bg-kumo-control px-1 py-0.5 text-sm">renderValue</code> to customize how the selected value
200-
appears in the trigger button. This is useful when working with complex object
201-
data structures instead of simple string values.
202-
</p>
223+
<p>
224+
Use{" "}
225+
<code class="rounded bg-kumo-control px-1 py-0.5 text-sm">renderValue</code>{" "}
226+
to customize how the selected value appears in the trigger button. This is
227+
useful when working with complex object data structures instead of simple
228+
string values.
229+
</p>
203230

204231
<ComponentExample demo="SelectCustomRenderingDemo">
205232
<SelectCustomRenderingDemo client:load />
@@ -253,11 +280,11 @@ export default function Example() {
253280

254281
### Loading
255282

256-
<p>
257-
A select component with loading state. The loading state is passed to
258-
the component via the <code
259-
class="rounded bg-kumo-control px-1 py-0.5 text-sm">loading</code> prop.
260-
</p>
283+
<p>
284+
A select component with loading state. The loading state is passed to the
285+
component via the{" "}
286+
<code class="rounded bg-kumo-control px-1 py-0.5 text-sm">loading</code> prop.
287+
</p>
261288

262289
<ComponentExample demo="SelectLoadingDemo">
263290
<div class="flex flex-col gap-4">
@@ -275,11 +302,15 @@ export default function Example() {
275302

276303
### Multiple Selection
277304

278-
<p>
279-
Enable multiple selection with the <code class="rounded bg-kumo-control px-1 py-0.5 text-sm">multiple</code> prop.
280-
The value becomes an array of selected items. Use <code class="rounded bg-kumo-control px-1 py-0.5 text-sm">placeholder</code> for the empty state
281-
and <code class="rounded bg-kumo-control px-1 py-0.5 text-sm">renderValue</code> to customize how selections are displayed.
282-
</p>
305+
<p>
306+
Enable multiple selection with the{" "}
307+
<code class="rounded bg-kumo-control px-1 py-0.5 text-sm">multiple</code>{" "}
308+
prop. The value becomes an array of selected items. Use{" "}
309+
<code class="rounded bg-kumo-control px-1 py-0.5 text-sm">placeholder</code>{" "}
310+
for the empty state and{" "}
311+
<code class="rounded bg-kumo-control px-1 py-0.5 text-sm">renderValue</code>{" "}
312+
to customize how selections are displayed.
313+
</p>
283314

284315
```tsx
285316
<Select
@@ -316,10 +347,11 @@ export default function Example() {
316347

317348
### Disabled Options
318349

319-
<p>
320-
Options can be disabled with the <code class="rounded bg-kumo-control px-1 py-0.5 text-sm">disabled</code> prop.
321-
Disabled options are greyed out and cannot be selected.
322-
</p>
350+
<p>
351+
Options can be disabled with the{" "}
352+
<code class="rounded bg-kumo-control px-1 py-0.5 text-sm">disabled</code>{" "}
353+
prop. Disabled options are greyed out and cannot be selected.
354+
</p>
323355

324356
<ComponentExample demo="SelectDisabledOptionsDemo">
325357
<SelectDisabledOptionsDemo client:load />
@@ -332,11 +364,12 @@ export default function Example() {
332364

333365
### Disabled Items (via items prop)
334366

335-
<p>
336-
The <code class="rounded bg-kumo-control px-1 py-0.5 text-sm">items</code> object-map prop
337-
accepts descriptor objects with <code class="rounded bg-kumo-control px-1 py-0.5 text-sm">disabled</code> alongside
338-
plain string values.
339-
</p>
367+
<p>
368+
The <code class="rounded bg-kumo-control px-1 py-0.5 text-sm">items</code>{" "}
369+
object-map prop accepts descriptor objects with{" "}
370+
<code class="rounded bg-kumo-control px-1 py-0.5 text-sm">disabled</code>{" "}
371+
alongside plain string values.
372+
</p>
340373

341374
<ComponentExample demo="SelectDisabledItemsDemo">
342375
<SelectDisabledItemsDemo client:load />
@@ -349,12 +382,18 @@ export default function Example() {
349382

350383
### Grouped Options
351384

352-
<p>
353-
Use <code class="rounded bg-kumo-control px-1 py-0.5 text-sm">Select.Group</code>,{" "}
354-
<code class="rounded bg-kumo-control px-1 py-0.5 text-sm">Select.GroupLabel</code>, and{" "}
355-
<code class="rounded bg-kumo-control px-1 py-0.5 text-sm">Select.Separator</code> to
356-
organize options under labeled headers with visual dividers.
357-
</p>
385+
<p>
386+
Use{" "}
387+
<code class="rounded bg-kumo-control px-1 py-0.5 text-sm">Select.Group</code>,{" "}
388+
<code class="rounded bg-kumo-control px-1 py-0.5 text-sm">
389+
Select.GroupLabel
390+
</code>
391+
, and{" "}
392+
<code class="rounded bg-kumo-control px-1 py-0.5 text-sm">
393+
Select.Separator
394+
</code>{" "}
395+
to organize options under labeled headers with visual dividers.
396+
</p>
358397

359398
<ComponentExample demo="SelectGroupedDemo">
360399
<SelectGroupedDemo client:load />
@@ -367,10 +406,10 @@ export default function Example() {
367406

368407
### Groups with Disabled Options
369408

370-
<p>
371-
Combine groups, separators, and disabled options with info tooltips to
372-
clearly separate available and unavailable choices.
373-
</p>
409+
<p>
410+
Combine groups, separators, and disabled options with info tooltips to clearly
411+
separate available and unavailable choices.
412+
</p>
374413

375414
<ComponentExample demo="SelectGroupedWithDisabledDemo">
376415
<SelectGroupedWithDisabledDemo client:load />
@@ -383,10 +422,10 @@ export default function Example() {
383422

384423
### Long List (Scrolling Test)
385424

386-
<p>
387-
A select component with many options to test popup scrolling behavior.
388-
The popup should scroll smoothly without bounce/overscroll issues.
389-
</p>
425+
<p>
426+
A select component with many options to test popup scrolling behavior. The
427+
popup should scroll smoothly without bounce/overscroll issues.
428+
</p>
390429

391430
<ComponentExample demo="SelectLongListDemo">
392431
<SelectLongListDemo client:load />
@@ -401,7 +440,7 @@ export default function Example() {
401440

402441
### Select
403442

404-
<PropsTable component="Select" />
443+
<PropsTable component="Select" />
405444

406445
### Select.Option
407446

0 commit comments

Comments
 (0)