Skip to content

Commit a737320

Browse files
committed
fix(ui): match text field active borders to design
Use the dedicated field editing color for Input and SearchField so native focus-visible heuristics do not replace the light 20% border with dark gray. Remove Design Lab's additional focus shadow and keep the existing 1px border and all control geometry unchanged. Preserve dark and high-contrast focus colors and inherit explicit legacy Appearance focus overrides unless an editing color is supplied. Keep the stronger generic focus contract available to its other control consumers. Update theme projections and regression checks. Validation: design-system:check, check:web, and 21 focused product tests.
1 parent 3e9f758 commit a737320

18 files changed

Lines changed: 52 additions & 24 deletions

File tree

‎design-system/apps/design-lab/src/styles.css‎

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,11 +1096,6 @@ body,
10961096
outline-offset: var(--openbitfun-focus-offset);
10971097
}
10981098

1099-
input.lab-force-focus {
1100-
border-color: var(--openbitfun-color-field-border-focus) !important;
1101-
box-shadow: 0 0 0 var(--openbitfun-focus-width) var(--openbitfun-color-focus-ring) !important;
1102-
}
1103-
11041099
[data-openbitfun-component="input"].lab-force-hover,
11051100
[data-openbitfun-component="search-field"].lab-force-hover [data-openbitfun-component="input"] {
11061101
border-color: var(--openbitfun-color-field-border-hover);
@@ -1109,8 +1104,8 @@ input.lab-force-focus {
11091104

11101105
[data-openbitfun-component="input"].lab-force-focus,
11111106
[data-openbitfun-component="search-field"].lab-force-focus [data-openbitfun-component="input"] {
1112-
border-color: var(--openbitfun-color-field-border-focus);
1113-
box-shadow: 0 0 0 var(--openbitfun-focus-width) var(--openbitfun-color-focus-ring);
1107+
border-color: var(--openbitfun-color-field-border-active);
1108+
box-shadow: none;
11141109
}
11151110

11161111
.component-code-panel {

‎design-system/apps/design-lab/vite/component-detail-contract.test.mjs‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,14 @@ test("Input, KeyHint, and SearchField previews expose composable slot and state
429429
assert.match(source, /shortcut=\{<KeyHint icon=\{<Icon name="command-mac" \/>\}>K<\/KeyHint>\}/);
430430
assert.match(source, /onClear=\{\(\) => setValue\(""\)\}/);
431431
assert.match(source, /readOnly=\{state === "read-only"\}/);
432+
433+
const styles = await readFile(stylesSource, "utf8");
434+
const fieldFocus = styles.match(/\[data-openbitfun-component="input"\]\.lab-force-focus,[^{]+\{([^}]+)\}/)?.[1];
435+
assert.ok(fieldFocus, "Input and SearchField must share their preview focus treatment");
436+
assert.match(fieldFocus, /border-color: var\(--openbitfun-color-field-border-active\)/);
437+
assert.match(fieldFocus, /box-shadow: none/);
438+
assert.doesNotMatch(fieldFocus, /border-width:|outline:|--openbitfun-focus-width/);
439+
assert.doesNotMatch(styles, /input\.lab-force-focus\s*\{/);
432440
});
433441

434442
test("ScrollArea preview exposes direction and native scrollbar visibility contracts", async () => {

‎design-system/packages/theme-openbitfun/README.md‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,22 @@ The same data is available as `@openbitfun/theme-openbitfun/reference-colors.jso
2929
## Surface and state roles
3030

3131
`color.field.*` owns shared field surfaces and state borders. Light uses neutral
32-
8% borders, with 20% hover and ordinary focus-within borders. Keyboard-visible
33-
focus retains the existing 3:1 contrast border in every mode; a browser may also
34-
show this stronger indicator when focusing an editable input with a pointer.
32+
8% borders, with 20% hover and active Input/SearchField borders. The dedicated
33+
`color.field.borderActive` owns the editing border because Input's design differs
34+
from the stronger focus treatment used by other controls. These text
35+
fields use one unchanged border for both pointer and keyboard focus; native
36+
`:focus-visible` must not substitute the stronger generic focus palette or add
37+
a second ring. Dark and high-contrast modes retain their own focus color, and forced
38+
colors use Highlight. `color.field.borderFocus` retains its 3:1 contract for
39+
other controls that consume that stronger focus treatment.
3540
`color.field.placeholder` separates
3641
40% empty hints and decorative adornments from general secondary prose; dark
3742
and high-contrast modes retain their readable muted content colors. The default
3843
light Web UI consumes these published values in root and chrome scopes. Named
3944
presets keep their own palette, and imported packages that only supply
4045
`color.content.muted` retain that field hint color unless explicitly overridden.
46+
Old packages that supply `color.field.borderFocus` retain that editing border
47+
unless they explicitly provide `color.field.borderActive`.
4148

4249
`component.button.*` owns Button's state palette. Its light fill stays at black
4350
8% while the shared neutral actions retain their 5/8/10% feedback; its primary

‎design-system/packages/theme-openbitfun/src/dark.tokens.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@
175175
}
176176
},
177177
"field": {
178+
"borderActive": { "$type": "color", "$value": "{color.field.borderFocus}" },
178179
"placeholder": { "$type": "color", "$value": "{color.content.muted}" },
179180
"background": { "$type": "color", "$value": "{ref.color.neutral.900}" },
180181
"backgroundHover": { "$type": "color", "$value": "rgba(255, 255, 255, 0.06)" },

‎design-system/packages/theme-openbitfun/src/high-contrast-dark.tokens.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
}
5252
},
5353
"field": {
54+
"borderActive": { "$type": "color", "$value": "{color.field.borderFocus}" },
5455
"placeholder": { "$type": "color", "$value": "{color.content.muted}" },
5556
"border": { "$type": "color", "$value": "{ref.color.gray.250}" },
5657
"borderHover": { "$type": "color", "$value": "{ref.color.gray.0}" },

‎design-system/packages/theme-openbitfun/src/high-contrast-light.tokens.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
}
5252
},
5353
"field": {
54+
"borderActive": { "$type": "color", "$value": "{color.field.borderFocus}" },
5455
"placeholder": { "$type": "color", "$value": "{color.content.muted}" },
5556
"border": { "$type": "color", "$value": "{ref.color.gray.650}" },
5657
"borderHover": { "$type": "color", "$value": "{ref.color.gray.1000}" },

‎design-system/packages/theme-openbitfun/src/light.tokens.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@
175175
}
176176
},
177177
"field": {
178+
"borderActive": { "$description": "Input and SearchField editing border, distinct from the stronger generic focus indicator used by other controls.", "$type": "color", "$value": "{color.field.borderHover}" },
178179
"placeholder": { "$description": "Empty field hints and decorative adornments; distinct from general secondary prose.", "$type": "color", "$value": "rgba(0, 0, 0, 0.40)" },
179180
"background": { "$type": "color", "$value": "{ref.color.neutral.0}" },
180181
"backgroundHover": { "$type": "color", "$value": "{color.field.background}" },

‎design-system/packages/ui/src/components/Input/Input.meta.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export const inputMeta = {
2828
"color.field.backgroundHover",
2929
"color.field.border",
3030
"color.field.borderHover",
31-
"color.field.borderFocus",
31+
"color.field.borderActive",
3232
"color.accent.default",
3333
"color.status.danger.border",
3434
"control.height.sm",

‎design-system/packages/ui/src/components/Input/Input.module.css‎

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,7 @@
2929
}
3030

3131
.field:focus-within {
32-
border-color: var(--openbitfun-color-field-border-hover);
33-
box-shadow: none;
34-
}
35-
36-
.field:has(:focus-visible) {
37-
border-color: var(--openbitfun-color-field-border-focus);
32+
border-color: var(--openbitfun-color-field-border-active);
3833
box-shadow: none;
3934
}
4035

@@ -124,8 +119,7 @@
124119
border-color: ButtonText;
125120
}
126121

127-
.field:focus-within,
128-
.field:has(:focus-visible) {
122+
.field:focus-within {
129123
border-color: Highlight;
130124
}
131125
}

‎design-system/packages/ui/src/components/SearchField/SearchField.meta.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export const searchFieldMeta = {
3636
"color.field.backgroundHover",
3737
"color.field.border",
3838
"color.field.borderHover",
39+
"color.field.borderActive",
3940
"color.field.borderFocus",
4041
"color.status.danger.border",
4142
"control.height.sm",

0 commit comments

Comments
 (0)