Skip to content

Align field to architecture goals#195

Merged
lifeiscontent merged 6 commits into
mainfrom
align/field
Jun 24, 2026
Merged

Align field to architecture goals#195
lifeiscontent merged 6 commits into
mainfrom
align/field

Conversation

@lifeiscontent

Copy link
Copy Markdown
Collaborator

Closes #128

What the designer's spec says

From the issue comment, the things that are always the same (baked in):

  • Vertical stack order: label → input → helper/error text
  • Label-to-input spacing, helper text position
  • Required asterisk position (always after label text)
  • Error text color (destructive token)
  • Label and helper text font styles

And the things that depend on usage (adjustable props):

  • Label text, the input component (text, textarea, select, …)
  • Helper text and error message text
  • Required vs optional
  • Disabled state

The component already honours this split well. These changes tighten the implementation layer without touching the public API.

Architecture changes

variants.ts

  • fieldDescriptionVariants and fieldErrorVariants were plain functions that called cx("text-tertiary", helperVariants({ magnitude })). Converted both to proper cva instances so all className composition lives in cva, not in ad-hoc wrapper functions.
  • inputFieldBoxVariants and textAreaFieldBoxVariants were plain functions that called cx(fieldBoxVariants(...), frameVariants(...)). Converted both to standalone cva instances with tone and magnitude variants inlined. The now-dead fieldBoxVariants base (which was only used as a private building block for those two functions) is removed.
  • iconSlotClass was a plain cx(...) constant. Renamed to iconSlotVariants and converted to cva.
  • Added fieldRootVariants cva for the Field.Root base class (was a bare string literal in the component file).

field.tsx

  • Calls fieldRootVariants() instead of inlining "flex flex-col gap-1.5" directly on the element.

input-field-icon-slot.tsx

  • Updated import from iconSlotClass to iconSlotVariants and calls iconSlotVariants().

text-area-field.tsx (components tier)

  • The control+helper wrapper was an anonymous <div className="flex w-full flex-col gap-1.5">. Replaced with <InputFieldContent orientation="vertical">, which renders the same classes via its cva variant and reuses the existing ui part rather than a bare div with inline style.

Notes

  • No visual change — the emitted class lists are identical.
  • No public API change — all prop types and exported names are unchanged.
  • vp check --no-fmt passes on all 39 affected files.
  • Needs design approval before merge.

@github-actions

Copy link
Copy Markdown

📚 Storybook preview: https://pr-195-propel-storybook.vamsi-906.workers.dev

- Convert fieldDescriptionVariants and fieldErrorVariants from plain cx-wrapper functions to proper cva instances, so className composition lives entirely in cva
- Convert inputFieldBoxVariants and textAreaFieldBoxVariants from cx-wrapper functions to standalone cva instances (tone + magnitude variants inlined); remove the now-dead fieldBoxVariants base
- Extract fieldRootVariants cva for Field.Root base class; call it from field.tsx instead of the bare inline string
- Rename iconSlotClass (plain cx constant) to iconSlotVariants (cva) and update the import in InputFieldIconSlot
- Replace the anonymous inline-className div in TextAreaField with InputFieldContent orientation="vertical", eliminating the last raw className in the components tier

Closes #128
FieldLabel was rendering its required asterisk as a bare second element
with an inline className, so the part owned two elements and baked styling
in. Pull the asterisk into a FieldLabelRequiredMarker part (single span,
its own cva), keeping FieldLabel a single Base UI Label that composes the
marker when required.

Move the remaining inline classNames in the field ui tier into cva: the
choice-option label/description column (fieldItemContentVariants) and the
inline icon slot, which now reuses the shared node-slot class instead of
baking a child size.
fieldRootVariants → fieldVariants (part: Field)
iconSlotVariants → inputFieldIconSlotVariants (part: InputFieldIconSlot)
labelGroupVariants → fieldLabelGroupVariants (part: FieldLabelGroup)
FieldLabelRequiredMarker no longer bakes the asterisk via children ?? "*";
it renders only its children. FieldLabel now passes the * glyph in when
required, and the UI story demonstrates the bare slot with an explicit glyph.
FieldHelperText composes two ui parts (FieldError + FieldDescription) with the
error-XOR-hint rule — it is a composition, not a single-element ui part, so it belongs
in components/field/ (where its 9 consumers already live), not ui/field/.
@lifeiscontent lifeiscontent merged commit 7775a7e into main Jun 24, 2026
2 checks passed
@lifeiscontent lifeiscontent deleted the align/field branch June 24, 2026 10:41
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.

Field: what should always look the same, and what should be adjustable?

1 participant