From ae04725632c88717bc7540bca22e3281651b7978 Mon Sep 17 00:00:00 2001 From: lmjabreu Date: Fri, 22 May 2026 12:25:43 +0100 Subject: [PATCH] feat(text-field): accept exceptionallySetClassName on the input element Lets callers attach a className to the underlying as an escape hatch when the design system can't yet express a needed styling hook. Mirrors the convention already used by Box, Tooltip, Loading, etc. Why this is needed: the deprecated component accepted a className on the underlying input element. Two real callsites depend on that affordance with no clean rewrite (an Adaptive Cards host that targets the input by class, and a borderless popover input). See ADR for full reasoning and the Twist discussion that established the convention. Decision: https://github.com/Doist/component-libraries/blob/main/decisions/2026-05-20-exceptionally-set-classname.md Twist thread: https://twist.com/a/1585/ch/81455/t/7796256/ Scope notes: - SelectField is intentionally excluded for now per the ADR caveat (the FormSelect/Adaptive Cards callsite is moving with a separate cleanup, not migrating). - BaseField needs no change; it does not directly render the inner field element. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/text-field/text-field.test.tsx | 11 +++++++++++ src/text-field/text-field.tsx | 6 +++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/text-field/text-field.test.tsx b/src/text-field/text-field.test.tsx index 61bff343..2be8cfe0 100644 --- a/src/text-field/text-field.test.tsx +++ b/src/text-field/text-field.test.tsx @@ -239,6 +239,17 @@ describe('TextField', () => { expect(inputElement).toHaveFocus() }) + it('forwards exceptionallySetClassName to the underlying input element', () => { + render( + , + ) + expect(screen.getByTestId('text-field')).toHaveClass('custom-input-class') + }) + describe('a11y', () => { it('renders with no a11y violations', async () => { const { container } = render( diff --git a/src/text-field/text-field.tsx b/src/text-field/text-field.tsx index 28819818..9bcb929f 100644 --- a/src/text-field/text-field.tsx +++ b/src/text-field/text-field.tsx @@ -8,13 +8,15 @@ import { Box } from '../box' import styles from './text-field.module.css' import type { BaseFieldProps, BaseFieldVariantProps, FieldComponentProps } from '../base-field' +import type { ObfuscatedClassName } from '../utils/common-types' type TextFieldType = 'email' | 'search' | 'tel' | 'text' | 'url' interface TextFieldProps extends Omit, 'type' | 'supportsStartAndEndSlots'>, BaseFieldVariantProps, - Pick { + Pick, + ObfuscatedClassName { type?: TextFieldType startSlot?: React.ReactElement | string | number endSlot?: React.ReactElement | string | number @@ -45,6 +47,7 @@ const TextField = React.forwardRef(function Te onChange: originalOnChange, characterCountPosition = 'below', endSlotPosition = 'bottom', + exceptionallySetClassName, ...props }, ref, @@ -107,6 +110,7 @@ const TextField = React.forwardRef(function Te type={type} ref={combinedRef} maxLength={maxLength} + className={exceptionallySetClassName} onChange={(event) => { originalOnChange?.(event) onChange?.(event)