+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ;
+}
+
export function ProviderConfigurationPattern() {
const { t } = useI18n();
const [open, setOpen] = useState(false);
diff --git a/design-system/packages/design-tokens/src/system.tokens.json b/design-system/packages/design-tokens/src/system.tokens.json
index bdf6db6ca5..20c9233835 100644
--- a/design-system/packages/design-tokens/src/system.tokens.json
+++ b/design-system/packages/design-tokens/src/system.tokens.json
@@ -169,6 +169,14 @@
"letterSpacing": { "$value": "{letterSpacing.normal}" }
}
},
+ "fieldHelper": {
+ "$description": "Field helper leading: 16px at the default 11px type size, scaling with user typography.",
+ "lineHeight": { "$type": "number", "$value": 1.4545454545454546 }
+ },
+ "formDescription": {
+ "$description": "Form-section description leading: 16px at the default 13px type size, scaling with user typography.",
+ "lineHeight": { "$type": "number", "$value": 1.2307692307692308 }
+ },
"support": {
"fontFamily": { "$value": "{font.family.control}" },
"fontSize": { "$value": "{font.size.meta}" },
diff --git a/design-system/packages/theme-openbitfun/README.md b/design-system/packages/theme-openbitfun/README.md
index 29f6a9d2a6..4afc95dbd8 100644
--- a/design-system/packages/theme-openbitfun/README.md
+++ b/design-system/packages/theme-openbitfun/README.md
@@ -62,7 +62,8 @@ component token is absent, and keeps explicit component overrides intact.
- `color.surface.scene`, `panel`, and `raised` own primary content and elevated planes.
- `color.surface.chrome` owns persistent application structure such as navigation and window-control regions.
-- `color.surface.tertiary` is an opaque low-emphasis fill for persistent grouped content such as cards and field groups.
+- `color.surface.tertiary` is an opaque low-emphasis fill for persistent containers such as cards.
+- `color.field.groupBackground` owns grouped form surfaces: light mode uses a 3% black tint so the underlying surface remains visible; dark and high-contrast modes retain their tertiary fill. Opaque tertiary containers and transient `surface.subtle` feedback cannot express this form-specific contract. Imported appearances inherit an explicitly supplied legacy tertiary color unless they supply the new field token.
- `color.surface.subtle` is a translucent local tint for transient feedback and small inset details. It must not define a persistent application plane.
- `color.selection.surface` owns persistent neutral selection. Hover and pressed colors remain action feedback and are not substitutes for selection.
- `color.codeChange.added` (`#1aa73e`) and `color.codeChange.removed` (`#ec221f`) also anchor success and danger emphasis. Warning emphasis uses `#ff8c00`; information uses the existing creative-action blue (`#2e7eff`). These clear hues share light tints instead of separate per-component palettes.
diff --git a/design-system/packages/theme-openbitfun/src/dark.tokens.json b/design-system/packages/theme-openbitfun/src/dark.tokens.json
index 153120ddd6..ec8d286684 100644
--- a/design-system/packages/theme-openbitfun/src/dark.tokens.json
+++ b/design-system/packages/theme-openbitfun/src/dark.tokens.json
@@ -175,6 +175,7 @@
}
},
"field": {
+ "groupBackground": { "$description": "Grouped form tint; independent of opaque tertiary containers and transient subtle feedback.", "$type": "color", "$value": "{color.surface.tertiary}" },
"borderActive": { "$type": "color", "$value": "{color.field.borderFocus}" },
"placeholder": { "$type": "color", "$value": "{color.content.muted}" },
"background": { "$type": "color", "$value": "{ref.color.neutral.900}" },
diff --git a/design-system/packages/theme-openbitfun/src/high-contrast-dark.tokens.json b/design-system/packages/theme-openbitfun/src/high-contrast-dark.tokens.json
index 5561e02ac7..30aa15e486 100644
--- a/design-system/packages/theme-openbitfun/src/high-contrast-dark.tokens.json
+++ b/design-system/packages/theme-openbitfun/src/high-contrast-dark.tokens.json
@@ -51,6 +51,7 @@
}
},
"field": {
+ "groupBackground": { "$description": "Grouped form tint; independent of opaque tertiary containers and transient subtle feedback.", "$type": "color", "$value": "{color.surface.tertiary}" },
"borderActive": { "$type": "color", "$value": "{color.field.borderFocus}" },
"placeholder": { "$type": "color", "$value": "{color.content.muted}" },
"border": { "$type": "color", "$value": "{ref.color.gray.250}" },
diff --git a/design-system/packages/theme-openbitfun/src/high-contrast-light.tokens.json b/design-system/packages/theme-openbitfun/src/high-contrast-light.tokens.json
index cc823429e7..0a66e1552f 100644
--- a/design-system/packages/theme-openbitfun/src/high-contrast-light.tokens.json
+++ b/design-system/packages/theme-openbitfun/src/high-contrast-light.tokens.json
@@ -51,6 +51,7 @@
}
},
"field": {
+ "groupBackground": { "$description": "Grouped form tint; independent of opaque tertiary containers and transient subtle feedback.", "$type": "color", "$value": "{color.surface.tertiary}" },
"borderActive": { "$type": "color", "$value": "{color.field.borderFocus}" },
"placeholder": { "$type": "color", "$value": "{color.content.muted}" },
"border": { "$type": "color", "$value": "{ref.color.gray.650}" },
diff --git a/design-system/packages/theme-openbitfun/src/light.tokens.json b/design-system/packages/theme-openbitfun/src/light.tokens.json
index 62f525e3fc..1578ff5c16 100644
--- a/design-system/packages/theme-openbitfun/src/light.tokens.json
+++ b/design-system/packages/theme-openbitfun/src/light.tokens.json
@@ -175,6 +175,7 @@
}
},
"field": {
+ "groupBackground": { "$description": "Grouped form tint; independent of opaque tertiary containers and transient subtle feedback.", "$type": "color", "$value": "rgba(0, 0, 0, 0.03)" },
"borderActive": { "$description": "Input and SearchField editing border, distinct from the stronger generic focus indicator used by other controls.", "$type": "color", "$value": "{color.field.borderHover}" },
"placeholder": { "$description": "Empty field hints and decorative adornments; distinct from general secondary prose.", "$type": "color", "$value": "rgba(0, 0, 0, 0.40)" },
"background": { "$type": "color", "$value": "{ref.color.neutral.0}" },
diff --git a/design-system/packages/ui/README.md b/design-system/packages/ui/README.md
index 5885a05f3b..3ef306285d 100644
--- a/design-system/packages/ui/README.md
+++ b/design-system/packages/ui/README.md
@@ -397,3 +397,11 @@ Tool-specific data shaping, localization, host actions, stores, and heavy
renderers remain in the consuming product and enter through semantic props,
callbacks, and slots. Bespoke product workflows remain product-owned rather
than being forced into a standard package view.
+
+Field labels follow their orientation: horizontal labels use the 13px semibold
+label role, while vertical labels use the 11px regular meta role. Field helpers
+use secondary content with 16px leading at the default 11px font size;
+FormSection descriptions use primary content with 16px leading at 13px. Both
+leading roles scale with user typography. FieldGroup uses the form group tint,
+retaining its existing row padding, dividers, and radius. The Patterns form
+specimen shows both orientations and long values over a tinted container.
diff --git a/design-system/packages/ui/src/components/Field/Field.meta.ts b/design-system/packages/ui/src/components/Field/Field.meta.ts
index 1d53681caf..735b0d542d 100644
--- a/design-system/packages/ui/src/components/Field/Field.meta.ts
+++ b/design-system/packages/ui/src/components/Field/Field.meta.ts
@@ -23,7 +23,7 @@ export const fieldMeta = {
tokens: [
"color.content.requiredIndicator",
"color.content.primary",
- "color.content.muted",
+ "color.content.secondary",
"color.status.danger.content",
"layout.field.rootGap",
"layout.field.horizontalGap",
@@ -35,7 +35,9 @@ export const fieldMeta = {
"layout.field.labelGap",
"layout.field.labelActionGap",
"layout.field.controlGap",
- "type.label.md.fontSize",
+ "type.label.selected.fontSize",
+ "type.meta.fontSize",
+ "type.fieldHelper.lineHeight",
"type.support.fontSize",
],
} as const satisfies ComponentMeta;
diff --git a/design-system/packages/ui/src/components/Field/Field.module.css b/design-system/packages/ui/src/components/Field/Field.module.css
index 01a7fe0c3a..c6ed724fb6 100644
--- a/design-system/packages/ui/src/components/Field/Field.module.css
+++ b/design-system/packages/ui/src/components/Field/Field.module.css
@@ -66,11 +66,19 @@
align-items: center;
gap: var(--openbitfun-layout-field-label-gap);
color: var(--openbitfun-color-content-primary);
- font-family: var(--openbitfun-type-label-md-font-family);
- font-size: var(--openbitfun-type-label-md-font-size);
- font-weight: var(--openbitfun-type-label-md-font-weight);
- line-height: var(--openbitfun-type-label-md-line-height);
- letter-spacing: var(--openbitfun-type-label-md-letter-spacing);
+ font-family: var(--openbitfun-type-meta-font-family);
+ font-size: var(--openbitfun-type-meta-font-size);
+ font-weight: var(--openbitfun-type-meta-font-weight);
+ line-height: var(--openbitfun-type-meta-line-height);
+ letter-spacing: var(--openbitfun-type-meta-letter-spacing);
+ }
+
+ .root[data-orientation="horizontal"] .label {
+ font-family: var(--openbitfun-type-label-selected-font-family);
+ font-size: var(--openbitfun-type-label-selected-font-size);
+ font-weight: var(--openbitfun-type-label-selected-font-weight);
+ line-height: var(--openbitfun-type-label-selected-line-height);
+ letter-spacing: var(--openbitfun-type-label-selected-letter-spacing);
}
.required {
@@ -85,11 +93,11 @@
}
.description {
- color: var(--openbitfun-color-content-muted);
+ color: var(--openbitfun-color-content-secondary);
font-family: var(--openbitfun-type-support-font-family);
font-size: var(--openbitfun-type-support-font-size);
font-weight: var(--openbitfun-type-support-font-weight);
- line-height: var(--openbitfun-type-support-line-height);
+ line-height: var(--openbitfun-type-field-helper-line-height);
letter-spacing: var(--openbitfun-type-support-letter-spacing);
}
diff --git a/design-system/packages/ui/src/components/FieldGroup/FieldGroup.meta.ts b/design-system/packages/ui/src/components/FieldGroup/FieldGroup.meta.ts
index 8f6bdc320e..efbb40b178 100644
--- a/design-system/packages/ui/src/components/FieldGroup/FieldGroup.meta.ts
+++ b/design-system/packages/ui/src/components/FieldGroup/FieldGroup.meta.ts
@@ -19,9 +19,8 @@ export const fieldGroupMeta = {
],
states: ["subtle", "plain", "divided"],
tokens: [
- "color.surface.tertiary",
+ "color.field.groupBackground",
"color.content.primary",
- "color.content.muted",
"color.border.subtle",
"color.field.background",
"color.field.backgroundHover",
@@ -30,6 +29,7 @@ export const fieldGroupMeta = {
"layout.formSection.titleDescriptionGap",
"type.heading.section.fontSize",
"type.body.sm.fontSize",
+ "type.formDescription.lineHeight",
"layout.fieldGroup.radius",
"layout.fieldGroup.rowPaddingBlock",
"layout.fieldGroup.rowPaddingInline",
diff --git a/design-system/packages/ui/src/components/FieldGroup/FieldGroup.module.css b/design-system/packages/ui/src/components/FieldGroup/FieldGroup.module.css
index 533ab09f75..d550fb16bf 100644
--- a/design-system/packages/ui/src/components/FieldGroup/FieldGroup.module.css
+++ b/design-system/packages/ui/src/components/FieldGroup/FieldGroup.module.css
@@ -63,11 +63,11 @@
}
.description {
- color: var(--openbitfun-color-content-muted);
+ color: var(--openbitfun-color-content-primary);
font-family: var(--openbitfun-type-body-sm-font-family);
font-size: var(--openbitfun-type-body-sm-font-size);
font-weight: var(--openbitfun-type-body-sm-font-weight);
- line-height: var(--openbitfun-type-body-sm-line-height);
+ line-height: var(--openbitfun-type-form-description-line-height);
letter-spacing: var(--openbitfun-type-body-sm-letter-spacing);
}
@@ -86,7 +86,7 @@
}
.group[data-appearance="subtle"] {
- background: var(--openbitfun-color-surface-tertiary);
+ background: var(--openbitfun-color-field-group-background);
}
.group[data-appearance="plain"] {
diff --git a/design-system/packages/ui/tests/field-group.test.mjs b/design-system/packages/ui/tests/field-group.test.mjs
index 25fdbaa5e4..672330e9ce 100644
--- a/design-system/packages/ui/tests/field-group.test.mjs
+++ b/design-system/packages/ui/tests/field-group.test.mjs
@@ -62,7 +62,7 @@ test("form grouping styles consume only shared public composition tokens", async
assert.match(styles, /--openbitfun-layout-form-section-gap/);
assert.match(styles, /--openbitfun-layout-field-group-radius/);
assert.match(styles, /--openbitfun-layout-field-group-row-padding-block/);
- assert.match(styles, /--openbitfun-color-surface-tertiary/);
+ assert.match(styles, /--openbitfun-color-field-group-background/);
assert.match(styles, /--openbitfun-color-border-subtle/);
});
diff --git a/design-system/packages/ui/tests/field.test.mjs b/design-system/packages/ui/tests/field.test.mjs
index e5c9583605..ea394226f7 100644
--- a/design-system/packages/ui/tests/field.test.mjs
+++ b/design-system/packages/ui/tests/field.test.mjs
@@ -98,10 +98,10 @@ test("Field styles consume shared content and typography tokens", async () => {
const styles = await readFile(new URL("../src/components/Field/Field.module.css", import.meta.url), "utf8");
assert.match(styles, /--openbitfun-color-content-primary/);
- assert.match(styles, /--openbitfun-color-content-muted/);
+ assert.match(styles, /--openbitfun-color-content-secondary/);
assert.match(styles, /--openbitfun-color-content-required-indicator/);
assert.doesNotMatch(styles, /--openbitfun-color-status-danger-content[^\n]*required/);
- assert.match(styles, /--openbitfun-type-label-md-font-size/);
+ assert.match(styles, /--openbitfun-type-label-selected-font-size/);
assert.match(styles, /--openbitfun-type-support-font-size/);
assert.match(styles, /--openbitfun-layout-field-root-gap/);
assert.match(styles, /--openbitfun-layout-field-label-action-gap/);
diff --git a/src/apps/data-migrator/ui/generated/design-system.css b/src/apps/data-migrator/ui/generated/design-system.css
index 54da7f2e68..2f22ed0dc8 100644
--- a/src/apps/data-migrator/ui/generated/design-system.css
+++ b/src/apps/data-migrator/ui/generated/design-system.css
@@ -463,6 +463,7 @@
--openbitfun-type-display-xxl-font-weight: var(--openbitfun-font-weight-semibold);
--openbitfun-type-display-xxl-letter-spacing: var(--openbitfun-letter-spacing-tight);
--openbitfun-type-display-xxl-line-height: var(--openbitfun-line-height-display);
+ --openbitfun-type-field-helper-line-height: 1.4545454545454546;
--openbitfun-type-flow-body-font-family: var(--openbitfun-font-family-sans);
--openbitfun-type-flow-body-font-size: var(--openbitfun-font-size-base);
--openbitfun-type-flow-body-font-weight: var(--openbitfun-font-weight-regular);
@@ -513,6 +514,7 @@
--openbitfun-type-flow-title-font-weight: var(--openbitfun-font-weight-semibold);
--openbitfun-type-flow-title-letter-spacing: var(--openbitfun-letter-spacing-snug);
--openbitfun-type-flow-title-line-height: var(--openbitfun-line-height-tight);
+ --openbitfun-type-form-description-line-height: 1.2307692307692308;
--openbitfun-type-heading-card-font-family: var(--openbitfun-font-family-control);
--openbitfun-type-heading-card-font-size: var(--openbitfun-font-size-sm);
--openbitfun-type-heading-card-font-weight: var(--openbitfun-font-weight-semibold);
@@ -728,6 +730,7 @@
--openbitfun-color-field-border-active: rgba(0, 0, 0, 0.20);
--openbitfun-color-field-border-focus: #858585;
--openbitfun-color-field-border-hover: rgba(0, 0, 0, 0.20);
+ --openbitfun-color-field-group-background: rgba(0, 0, 0, 0.03);
--openbitfun-color-field-placeholder: rgba(0, 0, 0, 0.40);
--openbitfun-color-focus-ring: #6a6a6a;
--openbitfun-color-identity-assistant-border: color-mix(in srgb, #db2777 15%, transparent);
@@ -875,6 +878,7 @@
--openbitfun-color-field-border-active: #858585;
--openbitfun-color-field-border-focus: #858585;
--openbitfun-color-field-border-hover: rgba(255, 255, 255, 0.24);
+ --openbitfun-color-field-group-background: #0e0e10;
--openbitfun-color-field-placeholder: #858585;
--openbitfun-color-focus-ring: #60a5fa;
--openbitfun-color-identity-assistant-border: color-mix(in srgb, #ec4899 15%, transparent);
@@ -993,6 +997,7 @@
--openbitfun-color-field-border-active: #005fcc;
--openbitfun-color-field-border-focus: #005fcc;
--openbitfun-color-field-border-hover: #000000;
+ --openbitfun-color-field-group-background: #f7f7f7;
--openbitfun-color-field-placeholder: #343434;
--openbitfun-color-focus-ring: #005fcc;
--openbitfun-color-identity-global-search-open-terminal: #000000;
@@ -1054,6 +1059,7 @@
--openbitfun-color-field-border-active: #ffcc00;
--openbitfun-color-field-border-focus: #ffcc00;
--openbitfun-color-field-border-hover: #ffffff;
+ --openbitfun-color-field-group-background: #000000;
--openbitfun-color-field-placeholder: #d0d0d0;
--openbitfun-color-focus-ring: #ffcc00;
--openbitfun-color-identity-global-search-open-terminal: #ffffff;
diff --git a/src/apps/desktop/src/generated/bootstrap_theme.css b/src/apps/desktop/src/generated/bootstrap_theme.css
index 80794639f6..f7431c6a52 100644
--- a/src/apps/desktop/src/generated/bootstrap_theme.css
+++ b/src/apps/desktop/src/generated/bootstrap_theme.css
@@ -63,6 +63,7 @@
--openbitfun-color-field-border-active: #858585;
--openbitfun-color-field-border-focus: #858585;
--openbitfun-color-field-border-hover: rgba(255, 255, 255, 0.24);
+ --openbitfun-color-field-group-background: #0e0e10;
--openbitfun-color-field-placeholder: #858585;
--openbitfun-color-focus-ring: #60a5fa;
--openbitfun-color-identity-assistant-border: color-mix(in srgb, #ec4899 15%, transparent);
diff --git a/src/web-ui/src/app/components/NewProjectDialog/NewProjectDialog.scss b/src/web-ui/src/app/components/NewProjectDialog/NewProjectDialog.scss
index a581ba8179..f5a1426ed1 100644
--- a/src/web-ui/src/app/components/NewProjectDialog/NewProjectDialog.scss
+++ b/src/web-ui/src/app/components/NewProjectDialog/NewProjectDialog.scss
@@ -6,7 +6,7 @@
&__content {
overflow: hidden;
- background: var(--openbitfun-color-surface-tertiary);
+ background: var(--openbitfun-color-field-group-background);
border-radius: var(--openbitfun-radius-lg);
}
@@ -29,10 +29,10 @@
&__preview-label {
color: var(--openbitfun-color-content-primary);
- font-family: var(--openbitfun-type-label-md-font-family);
- font-size: var(--openbitfun-type-label-md-font-size);
- font-weight: var(--openbitfun-type-label-md-font-weight);
- line-height: var(--openbitfun-type-label-md-line-height);
+ font-family: var(--openbitfun-type-meta-font-family);
+ font-size: var(--openbitfun-type-meta-font-size);
+ font-weight: var(--openbitfun-type-meta-font-weight);
+ line-height: var(--openbitfun-type-meta-line-height);
}
&__preview-path {
diff --git a/src/web-ui/src/infrastructure/appearance/builtins/appearancePresetOutput.test.ts b/src/web-ui/src/infrastructure/appearance/builtins/appearancePresetOutput.test.ts
index a23ce4a7ca..ac87080a1a 100644
--- a/src/web-ui/src/infrastructure/appearance/builtins/appearancePresetOutput.test.ts
+++ b/src/web-ui/src/infrastructure/appearance/builtins/appearancePresetOutput.test.ts
@@ -56,6 +56,7 @@ describe('builtin appearance preset output', () => {
expect(settings?.tokens['--openbitfun-color-field-border-hover']).toBe('rgba(0, 0, 0, 0.20)');
expect(settings?.tokens['--openbitfun-color-field-border-active']).toBe('rgba(0, 0, 0, 0.20)');
expect(settings?.tokens['--openbitfun-color-field-border-focus']).toBe(themes.light['color.field.borderFocus']);
+ expect(settings?.tokens['--openbitfun-color-field-group-background']).toBe('rgba(0, 0, 0, 0.03)');
expect(settings?.tokens['--openbitfun-color-field-placeholder']).toBe('rgba(0, 0, 0, 0.40)');
for (const palette of builtinAppearancePalettes) {
if (palette.id === 'openbitfun-light') continue;
@@ -63,6 +64,7 @@ describe('builtin appearance preset output', () => {
expect(tokens['--openbitfun-color-field-border']).toBe(palette.colors.border.base);
expect(tokens['--openbitfun-color-field-border-focus']).toBe(palette.colors.accent[500]);
expect(tokens['--openbitfun-color-field-border-active']).toBe(palette.colors.accent[500]);
+ expect(tokens['--openbitfun-color-field-group-background']).toBe(palette.colors.background.tertiary);
expect(tokens['--openbitfun-color-field-placeholder']).toBe(palette.colors.text.muted);
}
});
diff --git a/src/web-ui/src/infrastructure/appearance/builtins/buildBuiltinAppearance.ts b/src/web-ui/src/infrastructure/appearance/builtins/buildBuiltinAppearance.ts
index 7ee70f7c76..ecb1881237 100644
--- a/src/web-ui/src/infrastructure/appearance/builtins/buildBuiltinAppearance.ts
+++ b/src/web-ui/src/infrastructure/appearance/builtins/buildBuiltinAppearance.ts
@@ -157,6 +157,7 @@ function createThemeTokenValues(palette: AppearancePalette): Record {
'--openbitfun-color-field-border': '#123456',
'--openbitfun-color-field-border-focus': '#654321',
'--openbitfun-color-content-muted': '#778899',
+ '--openbitfun-color-surface-tertiary': '#abcdef',
},
- scopes: { chrome: { '--openbitfun-color-content-muted': '#556677', '--openbitfun-color-field-border-focus': '#445566' } },
+ scopes: { chrome: { '--openbitfun-color-content-muted': '#556677', '--openbitfun-color-field-border-focus': '#445566', '--openbitfun-color-surface-tertiary': '#aabbcc' } },
},
},
},
@@ -30,7 +31,9 @@ describe('composeAppearancePackage', () => {
'--openbitfun-color-field-border-focus': '#654321',
'--openbitfun-color-field-border-active': '#654321',
'--openbitfun-color-field-placeholder': '#778899',
+ '--openbitfun-color-field-group-background': '#abcdef',
});
+ expect(settings.scopes?.chrome?.['--openbitfun-color-field-group-background']).toBe('#aabbcc');
expect(settings.scopes?.chrome?.['--openbitfun-color-field-placeholder']).toBe('#556677');
expect(settings.scopes?.chrome?.['--openbitfun-color-field-border-active']).toBe('#445566');
expect(composeAppearancePackage(JSON.parse(JSON.stringify(resolved))).renderers?.['theme-tokens']).toEqual(resolved.renderers?.['theme-tokens']);
@@ -40,7 +43,11 @@ describe('composeAppearancePackage', () => {
original.renderers!['theme-tokens']!.settings.tokens['--openbitfun-color-field-border-active'] = '#223344';
original.renderers!['theme-tokens']!.settings.scopes!.chrome!['--openbitfun-color-field-placeholder'] = '#334455';
original.renderers!['theme-tokens']!.settings.scopes!.chrome!['--openbitfun-color-field-border-active'] = '#556688';
+ original.renderers!['theme-tokens']!.settings.tokens['--openbitfun-color-field-group-background'] = '#123abc';
+ original.renderers!['theme-tokens']!.settings.scopes!.chrome!['--openbitfun-color-field-group-background'] = '#456def';
const explicit = composeAppearancePackage(original).renderers!['theme-tokens']!.settings;
+ expect(explicit.tokens['--openbitfun-color-field-group-background']).toBe('#123abc');
+ expect(explicit.scopes?.chrome?.['--openbitfun-color-field-group-background']).toBe('#456def');
expect(explicit.tokens['--openbitfun-color-field-placeholder']).toBe('#112233');
expect(explicit.tokens['--openbitfun-color-field-border-active']).toBe('#223344');
expect(explicit.scopes?.chrome?.['--openbitfun-color-field-placeholder']).toBe('#334455');
diff --git a/src/web-ui/src/infrastructure/appearance/builtins/fieldThemeCompatibility.ts b/src/web-ui/src/infrastructure/appearance/builtins/fieldThemeCompatibility.ts
index fce11ae98b..da41d55a76 100644
--- a/src/web-ui/src/infrastructure/appearance/builtins/fieldThemeCompatibility.ts
+++ b/src/web-ui/src/infrastructure/appearance/builtins/fieldThemeCompatibility.ts
@@ -14,5 +14,9 @@ export function withLegacyFieldTokens(
if (result['--openbitfun-color-field-border-active'] === undefined && legacyFocus !== undefined) {
result['--openbitfun-color-field-border-active'] = legacyFocus;
}
+ const legacyGroup = tokens?.['--openbitfun-color-surface-tertiary'];
+ if (result['--openbitfun-color-field-group-background'] === undefined && legacyGroup !== undefined) {
+ result['--openbitfun-color-field-group-background'] = legacyGroup;
+ }
return result;
}
diff --git a/src/web-ui/src/infrastructure/config/components/ConfigForm.scss b/src/web-ui/src/infrastructure/config/components/ConfigForm.scss
index a6cef30ee3..3fc00259aa 100644
--- a/src/web-ui/src/infrastructure/config/components/ConfigForm.scss
+++ b/src/web-ui/src/infrastructure/config/components/ConfigForm.scss
@@ -63,7 +63,7 @@ $config-input-height: 32px;
font-weight: var(--openbitfun-type-support-font-weight);
color: var(--openbitfun-color-content-secondary);
margin-top: var(--openbitfun-space-1);
- line-height: var(--openbitfun-type-modifier-leading-support-line-height);
+ line-height: var(--openbitfun-type-field-helper-line-height);
letter-spacing: var(--openbitfun-type-support-letter-spacing);
overflow-wrap: break-word;
}
@@ -414,7 +414,7 @@ $config-input-height: 32px;
font-weight: var(--openbitfun-type-body-sm-font-weight);
color: var(--openbitfun-color-content-primary);
margin: 0 0 var(--openbitfun-space-5) 0;
- line-height: var(--openbitfun-type-body-sm-line-height);
+ line-height: var(--openbitfun-type-form-description-line-height);
letter-spacing: var(--openbitfun-type-body-sm-letter-spacing);
overflow-wrap: break-word;
}
diff --git a/src/web-ui/src/tools/generative-widget/appearancePayload.test.ts b/src/web-ui/src/tools/generative-widget/appearancePayload.test.ts
index 7129b89d90..269183bf32 100644
--- a/src/web-ui/src/tools/generative-widget/appearancePayload.test.ts
+++ b/src/web-ui/src/tools/generative-widget/appearancePayload.test.ts
@@ -40,6 +40,9 @@ const FIELD_STATE_THEME_VARIABLE_NAMES = [
'--openbitfun-color-field-border-active',
'--openbitfun-color-field-placeholder',
] as const;
+// Grouped forms own a translucent fill independently of opaque tertiary surfaces
+// and transient subtle feedback; keep this addition outside the shared fingerprint.
+const FIELD_GROUP_THEME_VARIABLE_NAME = '--openbitfun-color-field-group-background';
const RETIRED_WIDGET_VARIABLE_NAMES = [
'--background-primary',
'--bg-primary',
@@ -87,9 +90,11 @@ describe('generated widget appearance payload contract', () => {
const sharedNames = WIDGET_APPEARANCE_VAR_NAMES.filter(name => (
!name.startsWith('--openbitfun-component-button-')
&& !FIELD_STATE_THEME_VARIABLE_NAMES.some(fieldName => fieldName === name)
+ && name !== FIELD_GROUP_THEME_VARIABLE_NAME
));
expect(buttonNames).toEqual(BUTTON_THEME_VARIABLE_NAMES);
expect(fieldStateNames).toEqual(FIELD_STATE_THEME_VARIABLE_NAMES);
+ expect(WIDGET_APPEARANCE_VAR_NAMES).toContain(FIELD_GROUP_THEME_VARIABLE_NAME);
expect({
count: sharedNames.length,
hash: hashNames(sharedNames),
@@ -149,6 +154,7 @@ describe('generated widget appearance payload contract', () => {
'--openbitfun-component-button-primary-background': '#303030',
'--openbitfun-component-button-fill-background': 'rgba(0, 0, 0, 0.08)',
'--openbitfun-color-field-border-active': 'rgba(0, 0, 0, 0.20)',
+ '--openbitfun-color-field-group-background': 'rgba(0, 0, 0, 0.03)',
'--openbitfun-color-field-placeholder': 'rgba(0, 0, 0, 0.40)',
'--openbitfun-color-status-danger-surface': 'rgba(200, 0, 0, 0.12)',
'--openbitfun-color-status-danger-border': '#303030',