Skip to content

Pret UI: put the other field controls on Input's field style - #6871

Open
burieberry wants to merge 2 commits into
mainfrom
cs-14140-pret-ui-field-controls
Open

burieberry wants to merge 2 commits into
mainfrom
cs-14140-pret-ui-field-controls

Conversation

@burieberry

@burieberry burieberry commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Background and Goal

CS-14140. Input, Select and Autocomplete share one field style. This PR puts the rest of Pret UI's field controls on it, so they all match Input or Select side by side. The field style is:

  • a 28px height (--boxel-form-control-height-sm)
  • Button's corner (calc(var(--radius) - 0.125rem))
  • --boxel-font-size-xs text, at least 16px on touch screens
  • a 1px --input border inside the box
  • Select's hover edge and a 2px --ring focus outline
  • --boxel-disabled-opacity

This covers the typed inputs (EmailInput, UrlInput, SearchInput, PhoneInput, PasswordInput, NumberInput), Textarea, InputGroup, Combobox, Lookup, NativeSelect, MultiSelect and TreeSelect.

Every select-type control now shows Select's caret, flipped while it's open. An invalid control has a --destructive-ink edge with no tint, whether the error comes from the control itself, a Field or a FormField. Field's and FormField's error messages drop the ✕ glyph, since the control's edge already shows the error, and FormField's message text is the severity's -ink, as on Input's error row. Each component's Theming doc is updated to match.

Where to start

  • internal/field-dress.gts: FieldDress is the wrapper Input, Textarea and the typed inputs share. It sets BoxelInput's look entirely through --boxel-* variables, never reaching into boxel-ui's markup. It also holds the hover, focus, disabled and invalid rules.
  • internal/caret.gts: the one chevron the select-type controls draw. Combobox and Lookup draw the same path on power-select's status icon.
  • native-select.gts, multi-select.gts and tree-select.gts: each trigger takes Select's style.
  • field.gts, form-field.gts and autocomplete.gts: the error states, matched to Input's. Field sets --input on itself, so every select-type trigger inside an invalid Field takes the error edge.
  • cue.gts and field-error.gts: a @glyph argument. Field turns Cue's glyph off; FormField turns off FieldError's error glyph only, so warnings and info keep theirs and severities stay apart without relying on color.
  • field.examples.gts: the six select-type controls in a Field, with an Invalid switch.

Key decisions and non-obvious mechanics

  • boxel-ui changes. variables.css gains --boxel-disabled-opacity: 0.45, so every Pret UI control dims by the same amount; CS-14339 updates ADR 009 to say the shared stylesheets take changes like this. Select's and Picker's triggers give their CaretDown a width and height, so the caret doesn't render at full size before the stylesheet loads.
  • Border, not box-shadow. Select and Autocomplete drew their edge as a box-shadow outside the box, which made them 1px larger on each side than a BoxelInput. Every field now has a 1px border inside the box, and focus draws a 2px outline over it (outline-offset: -1px), which still shows in forced-colors mode.
  • A disabled field drops its error edge. It can't be fixed, so the edge goes back to --input.
  • Read-only isn't dimmed. It stays focusable and its text can still be copied.
  • Search glyph. SearchInput and Input @type='search' share internal/search-glyph.gts. It's a 14px magnifier that starts where text starts in the other fields, so neither needs BoxelInput's fixed 20px icon.
  • Buttons inside a field. SearchInput's clear button and PasswordInput's reveal button sit the same distance from the field's top and end edges, with the field's corner less that inset, and draw the 2px --ring inside themselves on focus. Host's global button focus rule reads its offset from --host-outline-offset, so the buttons set that too.
  • power-select's status icon. power-select positions it with a physical right: 5px. Combobox and Lookup reset that with right: auto before setting inset-inline-end, so the caret sits at Select's inset in both text directions.
  • Follow-ups:
    • The Cardstack brand guide's values, to match the default theme: CS-13140
    • Field controls that draw their own box (Cascader, DateTimePicker, ColorField, TimeInput, ScrubInput, AmountInput, OtpInput, Stepper, PeriodInput, Mentions, TokenInput): CS-14336
    • One error look for Field and Input, including the validation icons: CS-14274
    • A busy state for fields: CS-14327
    • Combobox's and Lookup's selection and dropdown look, matched to Select's: CS-14278
    • A Button icon before or after the label, which retires Autocomplete's clear-icon centering rule: CS-14279
    • ButtonGroup's hover background in light mode: CS-14280
    • Merging EmailInput into Input, and putting caller attributes on the typed inputs' inner input: CS-13375
    • Replacing BoxelInput inside Pret UI Input with a native input: CS-14159
    • Undefined Pret tokens left in the Combobox, Lookup and MultiSelect dropdowns and chips, FormField's description text and Password's strength meter: CS-13596

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Preview deployments

Host Test Results

    1 files      1 suites   2h 14m 46s ⏱️
5 923 tests 5 912 ✅ 11 💤 0 ❌
5 938 runs  5 927 ✅ 11 💤 0 ❌

Results for commit 9d5777c.

Realm Server Test Results

    1 files  ±0    363 suites  ±0   1h 50m 22s ⏱️ - 20m 4s
6 036 tests ±0  6 036 ✅ ±0  0 💤 ±0  0 ❌ ±0 
6 099 runs  ±0  6 099 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit 9d5777c. ± Comparison against earlier commit 37fe88b.

@burieberry
burieberry force-pushed the cs-14140-pret-ui-field-controls branch from f518d72 to 1ed7e8e Compare October 20, 2026 23:07
@burieberry burieberry self-assigned this Oct 22, 2026
@burieberry
burieberry changed the base branch from main to cs-14094-pret-ui-input-states October 22, 2026 17:49
@burieberry
burieberry force-pushed the cs-14094-pret-ui-input-states branch 2 times, most recently from 0422de6 to 69a22c1 Compare October 23, 2026 13:55
Base automatically changed from cs-14094-pret-ui-input-states to main October 23, 2026 15:43
@burieberry
burieberry force-pushed the cs-14140-pret-ui-field-controls branch 3 times, most recently from 6c75828 to 37fe88b Compare October 23, 2026 21:21
@burieberry
burieberry force-pushed the cs-14140-pret-ui-field-controls branch from 37fe88b to 9d5777c Compare October 23, 2026 22:31
@burieberry
burieberry marked this pull request as ready for review October 23, 2026 22:33
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-23T22:36:07.145851Z 9d5777c Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9d5777c595

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +170 to +171
.pretui-fieldw[data-invalid]:not(:has(:disabled, [aria-disabled='true'])) {
--input: var(--destructive-ink);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Limit disabled detection to the field control

When an invalid Field contains an enabled picker with a disabled option, this descendant-wide :has([aria-disabled='true']) becomes true and stops applying the error-edge token. For example, Autocomplete and TreeSelect render disabled options beneath the field while their popups are open (autocomplete.gts lines 771–819 and tree-select.gts lines 371–395), so merely opening such a picker makes the invalid border disappear even though the control remains enabled. Restrict this check to the actual control/trigger rather than every descendant.

Useful? React with 👍 / 👎.

@richardhjtan richardhjtan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Claude Code 🤖] This review went after the shared field style's state rules (invalid, disabled, focus) across Field and the select-type controls.

One blocking issue: an invalid Field loses its error edge whenever an enabled picker inside it opens a popup with a disabled option.

  1. field.gts: the disabled exemption on the invalid rule matches descendants of the popup, not only the control (inline comment; this agrees with the existing Codex thread on the same line).

hue misses a field boundary's 3:1) and no tint, through --input,
which the select-type triggers draw their edge from. A disabled
control can't be fixed, so it keeps the usual edge. */
.pretui-fieldw[data-invalid]:not(:has(:disabled, [aria-disabled='true'])) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Claude Code 🤖] Blocking, regression. :has(:disabled, [aria-disabled='true']) matches any descendant of the Field, and TreeSelect's tree and Autocomplete's listbox render inline inside it with aria-disabled='true' on disabled rows. So an invalid Field holding an enabled TreeSelect or Autocomplete with one disabled option drops --input: var(--destructive-ink) the moment the popup opens, and the error edge comes back when it closes. Can the exemption ignore popup contents, e.g. :not(:has(:is(:disabled, [aria-disabled='true']):not([role='listbox'] *, [role='tree'] *))), or key off a marker the control's own trigger/input sets? A Field test with an open TreeSelect holding a disabled node would pin it.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants