Files
openchamber/.agents/skills/settings-ui-patterns/references/controls.md
T
ChangeHowandBohdan Triapitsyn 5ae1a949c8 fix(ui): improve composer focus, keyboard navigation, and settings (#3376)
* fix(ui): make composer keyboard interactions consistent

* docs(settings): refine description visibility guidance

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-09-07 20:30:19 +03:00

4.4 KiB

Settings Controls

Load theme-system for button/icon/color contracts and locale-ui-patterns for every visible or accessible string. All primitives/constants come from packages/ui/src/components/sections/shared/SettingsSection.tsx (+ SettingsInfoHint.tsx).

Standard Sizes And Widths

One control size across Settings — h-8:

  • SelectTrigger: size={SETTINGS_SELECT_SIZE} ('settings' → h-8, rounded-md, px-3).
  • Custom dropdown triggers (ModelSelector / AgentSelector): SETTINGS_CUSTOM_TRIGGER_CLASS.
  • Text Input next to dropdowns: h-8 rounded-md px-3 (match the trigger footprint).
  • Icon action next to a control: SETTINGS_ICON_BUTTON_CLASS.

Widths are capped — never let controls span the pane:

  • Field-row control cluster / stacked-field default cap: max-w-[24rem] (built into SettingsStackedField; use SETTINGS_CONTROL_CLUSTER_CLASS elsewhere).
  • Field-row selects: SETTINGS_SELECT_ROW_TRIGGER_CLASS (full width narrow, @xl:w-56 wide).
  • Stacked-field selects: SETTINGS_SELECT_TRIGGER_CLASS (fills the capped container).
  • Genuinely full-width content (dialog textareas): opt out with controlClassName="w-full max-w-none".

Field Rows

<SettingsFieldRow
  label={t('...label')}
  info={t('...hint')}                 // helper text behind the info icon
  settingsItem="page.some-setting"
>
  <Select >
    <SelectTrigger size={SETTINGS_SELECT_SIZE} className={SETTINGS_SELECT_ROW_TRIGGER_CLASS} aria-label={t('...aria')}>

Use SettingsStackedField (label above control) inside SettingsTwoColumn cells or when the control is wide; same info / settingsItem props.

Boolean

For a self-explanatory enable/disable setting, use only a checkbox and label; no separate group title or description is needed.

<SettingsCheckboxRow
  checked={value}
  onChange={setValue}
  label={t('...label')}
  ariaLabel={t('...aria')}
  info={t('...explanation')}          // optional; see Description Policy
  settingsItem="page.some-setting"
/>

Row click + keyboard toggling are built in. A visible description is only for text that must stay visible (warnings, dynamic status).

Mutually Exclusive Options

Use radios for mutually exclusive modes, not independent checkboxes. Keep self-explanatory choices compact; a title or description is not mandatory.

When the skill's Description Policy calls for a visible explanation, wrap the controls in SettingsControlGroup: title, description, then options. Explain the choice once at group level. Use checkbox rows for independent choices and radio options for mutually exclusive choices. Group spacing is defined in layout.md.

<SettingsControlGroup title={t('...group')} description={t('...description')}>
  <SettingsRadioGroup aria-label={t('...group')}>
    <SettingsRadioOption selected={} onSelect={} label={t('...')} ariaLabel={t('...')} />
  </SettingsRadioGroup>
</SettingsControlGroup>

Skip per-option descriptions when labels are self-explanatory. For short segmented choices use SettingsChipGroup (chips with aria-pressed).

Numeric Value / Override

NumberInput inside SETTINGS_NUMBER_STEPPER_ROW_CLASS, with SETTINGS_NUMBER_UNIT_CLASS for the unit and an adjacent SETTINGS_ICON_BUTTON_CLASS reset button. Never flex-grow the stepper. Optional overrides: empty means "inherit"; provide fallbackValue, onClear, emptyLabel="—".

Info Hints

SettingsInfoHint is the only info-icon implementation: it opens on hover AND on click (touch devices have no hover), and closes on outside tap. Prefer the info prop of the enclosing primitive; use the component directly only next to raw labels/headings. Never build info icons from raw <Tooltip> + <Icon name="information"> — those don't work on mobile.

Mobile Constraints

  • packages/ui/src/styles/mobile.css may force .overflow-hidden to scroll; use explicit x/y clipping only when required.
  • Touch CSS enforces minimum button height. Do not put custom segmented buttons in a container too short for them.

Picker Rows

  • Place icon/color palettes beneath their label.
  • Keep option dimensions and gaps consistent.
  • Use stable border/ring/background selection; avoid scale transforms that shift layout.

Dialogs

Dialogs reuse the same primitives (SettingsCheckboxRow, SETTINGS_FIELD_LABEL_CLASS, SettingsStackedField) and the same sizes. Dividers between dialog form groups are acceptable; wizard step instructions guiding an active flow stay visible (not behind info).