Skip to content

feat: Textfield prefix followup#10014

Open
snowystinger wants to merge 12 commits intomainfrom
textfield-prefix-followup
Open

feat: Textfield prefix followup#10014
snowystinger wants to merge 12 commits intomainfrom
textfield-prefix-followup

Conversation

@snowystinger
Copy link
Copy Markdown
Member

Closes

Team talked about supporting more things in the prefix slot today.

It center baselines everything automatically for alignment. Should it also set size on avatar and swatch? that's more things that each field has to pull in.

This also adds an id to associate with the input's aria-labelledby so it's announced when a user arrives in an input.

✅ Pull Request Checklist:

  • Included link to corresponding React Spectrum GitHub Issue.
  • Added/updated unit tests and storybook for this change (for new code or code which already has tests).
  • Filled out test instructions.
  • Updated documentation (if it already exists for this component).
  • Looked at the Accessibility Practices for this feature - Aria Practices

📝 Test Instructions:

🧢 Your Project:

@rspbot
Copy link
Copy Markdown

rspbot commented May 5, 2026

@snowystinger snowystinger mentioned this pull request May 5, 2026
5 tasks
Comment thread packages/@react-spectrum/s2/src/ColorField.tsx Outdated
LFDanLu
LFDanLu previously approved these changes May 6, 2026
Copy link
Copy Markdown
Member

@LFDanLu LFDanLu left a comment

Choose a reason for hiding this comment

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

I'm ok with the current API since the user can still scale up the swatch/avatar/arbitrary element if they want to. We can add auto scaling for those later if need be

devongovett
devongovett previously approved these changes May 7, 2026
Copy link
Copy Markdown
Member

@devongovett devongovett left a comment

Choose a reason for hiding this comment

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

Need to add to the docs as well.

Comment thread packages/@react-spectrum/s2/src/ComboBox.tsx Outdated
@snowystinger
Copy link
Copy Markdown
Member Author

doh, I'll add the docs now, completely slipped my mind

@snowystinger snowystinger dismissed stale reviews from devongovett and LFDanLu via 35d3e3b May 8, 2026 00:33
@rspbot
Copy link
Copy Markdown

rspbot commented May 8, 2026

@rspbot
Copy link
Copy Markdown

rspbot commented May 8, 2026

@rspbot
Copy link
Copy Markdown

rspbot commented May 8, 2026

devongovett
devongovett previously approved these changes May 8, 2026
Copy link
Copy Markdown
Member

@devongovett devongovett left a comment

Choose a reason for hiding this comment

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

I kinda wish prefix was a control in the main example you could choose from rather than a whole separate section... Usually our display props are just controls. But not sure what the options would be. Maybe we can think about that.

}
```

## Prefix
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this should go in the Content section. I guess the other fields don't have that so it's ok for them. Also I guess it's a little strange that the avatar doesn't change depending on which user you selected?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

yeah.... i wasn't sure what the empty state would be for the avatar and didn't want to complicate the logic with that

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Also, I didn't want this in the Content section because that's really all about collections

@snowystinger
Copy link
Copy Markdown
Member Author

Usually our display props are just controls. But not sure what the options would be. Maybe we can think about that.

Yeah, I wanted to make it a control, but then it was a question of string or icon or a picker with both + avatar and swatch etc?

In ColorField I also wanted to hook up the swatch to the value. I could put it into the controlled value example instead though.

@rspbot
Copy link
Copy Markdown

rspbot commented May 8, 2026

@rspbot
Copy link
Copy Markdown

rspbot commented May 8, 2026

@rspbot
Copy link
Copy Markdown

rspbot commented May 8, 2026

## API Changes

@react-spectrum/s2

/@react-spectrum/s2:ColorField

 ColorField {
   UNSAFE_className?: UnsafeClassName
   UNSAFE_style?: CSSProperties
   aria-describedby?: string
   aria-details?: string
   aria-errormessage?: string
   aria-label?: string
   aria-labelledby?: string
   autoFocus?: boolean
   channel?: ColorChannel
   colorSpace?: ColorSpace
   contextualHelp?: ReactNode
   defaultValue?: T
   description?: ReactNode
   errorMessage?: ReactNode | (ValidationResult) => ReactNode
   excludeFromTabOrder?: boolean
   form?: string
   id?: string
   isDisabled?: boolean
   isInvalid?: boolean
   isReadOnly?: boolean
   isRequired?: boolean
   isWheelDisabled?: boolean
   label?: ReactNode
   labelAlign?: Alignment = 'start'
   labelPosition?: LabelPosition = 'top'
   name?: string
   necessityIndicator?: NecessityIndicator = 'icon'
   onBeforeInput?: FormEventHandler<T>
   onBlur?: (FocusEvent<Target>) => void
   onChange?: (Color | null) => void
   onCompositionEnd?: CompositionEventHandler<T>
   onCompositionStart?: CompositionEventHandler<T>
   onCompositionUpdate?: CompositionEventHandler<T>
   onCopy?: ClipboardEventHandler<T>
   onCut?: ClipboardEventHandler<T>
   onFocus?: (FocusEvent<Target>) => void
   onFocusChange?: (boolean) => void
   onInput?: FormEventHandler<T>
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onPaste?: ClipboardEventHandler<T>
   onSelect?: ReactEventHandler<T>
   placeholder?: string
+  prefix?: ReactNode
   size?: 'S' | 'M' | 'L' | 'XL' = 'M'
   slot?: string | null
   styles?: StylesProp
   validate?: (Color | null) => ValidationError | boolean | null | undefined
   value?: T
 }

/@react-spectrum/s2:ComboBox

 ComboBox <T extends {}> {
   UNSAFE_className?: UnsafeClassName
   UNSAFE_style?: CSSProperties
   align?: 'start' | 'end' = 'start'
   allowsCustomValue?: boolean
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoFocus?: boolean
   children: ReactNode | ({}) => ReactNode
   contextualHelp?: ReactNode
   defaultInputValue?: string
   defaultItems?: Iterable<T>
   defaultSelectedKey?: Key | null
   dependencies?: ReadonlyArray<any>
   description?: ReactNode
   direction?: 'bottom' | 'top' = 'bottom'
   disabledKeys?: Iterable<Key>
   errorMessage?: ReactNode | (ValidationResult) => ReactNode
   form?: string
   formValue?: 'text' | 'key' = 'key'
   id?: string
   inputValue?: string
   isDisabled?: boolean
   isInvalid?: boolean
   isReadOnly?: boolean
   isRequired?: boolean
   items?: Iterable<T>
   label?: ReactNode
   labelAlign?: Alignment = 'start'
   labelPosition?: LabelPosition = 'top'
   loadingState?: LoadingState
   menuTrigger?: MenuTriggerAction = 'input'
   menuWidth?: number
   name?: string
   necessityIndicator?: NecessityIndicator = 'icon'
   onBlur?: (FocusEvent<HTMLInputElement>) => void
   onFocus?: (FocusEvent<HTMLInputElement>) => void
   onFocusChange?: (boolean) => void
   onInputChange?: (string) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onLoadMore?: () => any
   onOpenChange?: (boolean, MenuTriggerAction) => void
   onSelectionChange?: (Key | null) => void
   placeholder?: string
+  prefix?: ReactNode
   selectedKey?: Key | null
   shouldFlip?: boolean = true
   shouldFocusWrap?: boolean
   size?: 'S' | 'M' | 'L' | 'XL' = 'M'
   styles?: StylesProp
   validate?: (ComboBoxValidationValue<SelectionMode>) => ValidationError | boolean | null | undefined
   validationBehavior?: 'native' | 'aria' = 'native'
 }

/@react-spectrum/s2:NumberField

 NumberField {
   UNSAFE_className?: UnsafeClassName
   UNSAFE_style?: CSSProperties
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoFocus?: boolean
   commitBehavior?: 'snap' | 'validate' = 'snap'
   contextualHelp?: ReactNode
   decrementAriaLabel?: string
   defaultValue?: number
   description?: ReactNode
   errorMessage?: ReactNode | (ValidationResult) => ReactNode
   form?: string
   formatOptions?: Intl.NumberFormatOptions
   hideStepper?: boolean = false
   id?: string
   incrementAriaLabel?: string
   isDisabled?: boolean
   isInvalid?: boolean
   isReadOnly?: boolean
   isRequired?: boolean
   isWheelDisabled?: boolean
   label?: ReactNode
   labelAlign?: Alignment = 'start'
   labelPosition?: LabelPosition = 'top'
   maxValue?: number
   minValue?: number
   name?: string
   necessityIndicator?: NecessityIndicator = 'icon'
   onBeforeInput?: FormEventHandler<T>
   onBlur?: (FocusEvent<Target>) => void
   onChange?: (T) => void
   onCompositionEnd?: CompositionEventHandler<T>
   onCompositionStart?: CompositionEventHandler<T>
   onCompositionUpdate?: CompositionEventHandler<T>
   onCopy?: ClipboardEventHandler<T>
   onCut?: ClipboardEventHandler<T>
   onFocus?: (FocusEvent<Target>) => void
   onFocusChange?: (boolean) => void
   onInput?: FormEventHandler<T>
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onPaste?: ClipboardEventHandler<T>
   onSelect?: ReactEventHandler<T>
   placeholder?: string
+  prefix?: ReactNode
   size?: 'S' | 'M' | 'L' | 'XL' = 'M'
   slot?: string | null
   step?: number
   styles?: StylesProp
   validationBehavior?: 'native' | 'aria' = 'native'
   value?: number
 }

/@react-spectrum/s2:ColorFieldProps

 ColorFieldProps {
   UNSAFE_className?: UnsafeClassName
   UNSAFE_style?: CSSProperties
   aria-describedby?: string
   aria-details?: string
   aria-errormessage?: string
   aria-label?: string
   aria-labelledby?: string
   autoFocus?: boolean
   channel?: ColorChannel
   colorSpace?: ColorSpace
   contextualHelp?: ReactNode
   defaultValue?: T
   description?: ReactNode
   errorMessage?: ReactNode | (ValidationResult) => ReactNode
   excludeFromTabOrder?: boolean
   form?: string
   id?: string
   isDisabled?: boolean
   isInvalid?: boolean
   isReadOnly?: boolean
   isRequired?: boolean
   isWheelDisabled?: boolean
   label?: ReactNode
   labelAlign?: Alignment = 'start'
   labelPosition?: LabelPosition = 'top'
   name?: string
   necessityIndicator?: NecessityIndicator = 'icon'
   onBeforeInput?: FormEventHandler<T>
   onBlur?: (FocusEvent<Target>) => void
   onChange?: (Color | null) => void
   onCompositionEnd?: CompositionEventHandler<T>
   onCompositionStart?: CompositionEventHandler<T>
   onCompositionUpdate?: CompositionEventHandler<T>
   onCopy?: ClipboardEventHandler<T>
   onCut?: ClipboardEventHandler<T>
   onFocus?: (FocusEvent<Target>) => void
   onFocusChange?: (boolean) => void
   onInput?: FormEventHandler<T>
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onPaste?: ClipboardEventHandler<T>
   onSelect?: ReactEventHandler<T>
   placeholder?: string
+  prefix?: ReactNode
   size?: 'S' | 'M' | 'L' | 'XL' = 'M'
   slot?: string | null
   styles?: StylesProp
   validate?: (Color | null) => ValidationError | boolean | null | undefined
   value?: T
 }

/@react-spectrum/s2:ComboBoxProps

 ComboBoxProps <T extends {}> {
   UNSAFE_className?: UnsafeClassName
   UNSAFE_style?: CSSProperties
   align?: 'start' | 'end' = 'start'
   allowsCustomValue?: boolean
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoFocus?: boolean
   children: ReactNode | ({}) => ReactNode
   contextualHelp?: ReactNode
   defaultInputValue?: string
   defaultItems?: Iterable<T>
   defaultSelectedKey?: Key | null
   dependencies?: ReadonlyArray<any>
   description?: ReactNode
   direction?: 'bottom' | 'top' = 'bottom'
   disabledKeys?: Iterable<Key>
   errorMessage?: ReactNode | (ValidationResult) => ReactNode
   form?: string
   formValue?: 'text' | 'key' = 'key'
   id?: string
   inputValue?: string
   isDisabled?: boolean
   isInvalid?: boolean
   isReadOnly?: boolean
   isRequired?: boolean
   items?: Iterable<T>
   label?: ReactNode
   labelAlign?: Alignment = 'start'
   labelPosition?: LabelPosition = 'top'
   loadingState?: LoadingState
   menuTrigger?: MenuTriggerAction = 'input'
   menuWidth?: number
   name?: string
   necessityIndicator?: NecessityIndicator = 'icon'
   onBlur?: (FocusEvent<HTMLInputElement>) => void
   onFocus?: (FocusEvent<HTMLInputElement>) => void
   onFocusChange?: (boolean) => void
   onInputChange?: (string) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onLoadMore?: () => any
   onOpenChange?: (boolean, MenuTriggerAction) => void
   onSelectionChange?: (Key | null) => void
   placeholder?: string
+  prefix?: ReactNode
   selectedKey?: Key | null
   shouldFlip?: boolean = true
   shouldFocusWrap?: boolean
   size?: 'S' | 'M' | 'L' | 'XL' = 'M'
   styles?: StylesProp
   validate?: (ComboBoxValidationValue<SelectionMode>) => ValidationError | boolean | null | undefined
   validationBehavior?: 'native' | 'aria' = 'native'
 }

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants