Skip to content

Conversation

@tomdavies73
Copy link
Contributor

@tomdavies73 tomdavies73 commented Sep 30, 2025

Proposed behaviour

Adds the TextInput component, a much simpler alternative to Textbox with a smaller, less complex interface. The component is built to the Design Systems specifications and uses the brand-new fusion tokens.

Screenshot 2025-11-25 at 16 58 33

The component disregards all current internal legacy and has been built from the ground up using basic HTML with additional styling added via designs, and some additional layout elements to support specific visual patterns. The component also supports the addition of hint text and error/warning messaging and borders.

The component interface is as close to the HTML elements attributes as possible, with only additional props added to aid specific necessary functionality or designs.

Current behaviour

Currently, there is no TextInput component, just a Textbox component which has been marked as legacy and is not aligned with the design system.

Checklist

  • Commits follow our style guide
  • Related issues linked in commit messages if required
  • Screenshots are included in the PR if useful
  • All themes are supported if required
  • Unit tests added or updated if required
  • Playwright automation tests added or updated if required
  • Storybook added or updated if required
  • Translations added or updated (including creating or amending translation keys table in storybook) if required
  • Typescript d.ts file added or updated if required
  • Related docs have been updated if required

QA

  • Tested in provided StackBlitz sandbox/Storybook
  • Add new Playwright test coverage if required
  • Carbon implementation matches Design System/designs
  • UI Tests GitHub check reviewed if required

Additional context

Testing instructions

@tomdavies73 tomdavies73 self-assigned this Sep 30, 2025
@tomdavies73 tomdavies73 requested review from a team as code owners September 30, 2025 15:46
@tomdavies73 tomdavies73 added DO NOT MERGE Work in progress This is a WIP PR so may not be ready for review labels Sep 30, 2025
@tomdavies73 tomdavies73 force-pushed the FE-7230 branch 2 times, most recently from 3fe2137 to cb48059 Compare October 7, 2025 15:54
@tomdavies73 tomdavies73 marked this pull request as draft November 10, 2025 16:00
@tomdavies73 tomdavies73 force-pushed the FE-7230 branch 2 times, most recently from 40ef696 to ee710ba Compare November 12, 2025 15:15
@tomdavies73 tomdavies73 marked this pull request as ready for review November 12, 2025 16:04
@tomdavies73 tomdavies73 force-pushed the FE-7230 branch 2 times, most recently from d4b47d4 to 6fedb6c Compare November 20, 2025 14:41
@tomdavies73 tomdavies73 requested a review from a team as a code owner November 20, 2025 14:41
@tomdavies73 tomdavies73 marked this pull request as draft November 20, 2025 15:45
@tomdavies73 tomdavies73 force-pushed the FE-7230 branch 4 times, most recently from 3ea2dc5 to 5997a89 Compare November 25, 2025 16:57
@tomdavies73 tomdavies73 added Pending Review Pending QA Pending UX QA and removed DO NOT MERGE Work in progress This is a WIP PR so may not be ready for review labels Nov 25, 2025
Copy link
Contributor

@edleeks87 edleeks87 left a comment

Choose a reason for hiding this comment

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

@ljemmo @harpalsingh do we have any designs for inline labels with an inputHint? the alignment looks a little off on this but I can't compare it to anything

Image

? "var(--input-validation-label-error)"
: "var(--input-validation-label-warn)"};
font-family: var(--fontFamiliesDefault);
Copy link
Contributor

Choose a reason for hiding this comment

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

question: why aren't we using the new tokens for the font?

Copy link
Contributor

Choose a reason for hiding this comment

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

comment: The font tokens contain everything already as far as I know so you could just set font unless I'm missing something, you might need to pick the relevant one based on size and error vs warning though (worth checking tokens with DS/UX though)

   --global-font-static-comp-medium-xs: 500 13px/1.5 Sage UI;
  --global-font-static-comp-medium-s: 500 14px/1.5 Sage UI;
  --global-font-static-comp-medium-m: 500 14px/1.5 Sage UI;
  --global-font-static-comp-medium-l: 500 16px/1.5 Sage UI;
  --global-font-static-comp-regular-xs: 400 13px/1.5 Sage UI;
  --global-font-static-comp-regular-s: 400 14px/1.5 Sage UI;
  --global-font-static-comp-regular-m: 400 14px/1.5 Sage UI;
  --global-font-static-comp-regular-l: 400 16px/1.5 Sage UI;
font-family: var(--fontFamiliesDefault);
font-size: ${isLarge
        ? "var(--global-font-static-body-regular-l)"
        : "var(--global-font-static-body-regular-m)"};

font-style: normal;
font-weight: ${isError ? "500" : "400"};
line-height: 150%;

can be

${isLarge && css`
  font: ${isError ? "var(--global-font-static-comp-medium-l)" : ""var(--global-font-static-comp-regular-l)"};
`}
${!isLarge && css`
     font: ${isError ? "var(--global-font-static-comp-medium-m)" : ""var(--global-font-static-comp-regular-m)"};
`}

Copy link
Contributor

Choose a reason for hiding this comment

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

@edleeks87 i don't believe there's any nice way to show hint text inline. When switching it on within figma it looks identical to Tom's implementation. TBF inline is generally discouraged. Where is see it used, it never has hint text enabled. e.g for quick filtering on top of tables. I think we have 2 options:

  1. Not allow hint text on inline and restrict from a code POV
  2. just leave as is knowing that its never going to be used and doesn't matter how it therefore appears.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think personally we should keep inline input hints, if we're providing the capacity to make labels inline via labelInline, it makes sense to have everything available in both label states. We've made accommodations for error states when the label is inline, it makes sense in my opinion to make those accommodations for the hint text too. What do we think @edleeks87 @ljemmo ?

? "var(--input-validation-bar-warn)"
: "var(--input-validation-border-error)"};
`}
transform: scaleX(1);
Copy link
Contributor

Choose a reason for hiding this comment

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

question: is this transform doing anything here as it's scaling to 100% with no animation?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So I added this as when the window is being repositioned the absolutely positioned error border can sometimes shrink when the label is inline. We didn't observe this previously as the error border was always in a fixed position, however when inline it is in the centre of the screen and needs to reposition. Adding the transform stopped this shrinking from occurring, and it appeared to be the only solution I could find that worked.

? "var(--input-labelset-label-disabled)"
: "var(--input-labelset-label-alt)"};
font-family: var(--fontFamiliesDefault);
Copy link
Contributor

Choose a reason for hiding this comment

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

Comment: same as above wrt to using font and the token etc

const stateProps = { disabled, readOnly };

useEffect(() => {
if (autoFocus && inputRef && "current" in inputRef) {
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (non-blocking): what you have is fine (don't need to check inputRef as if current is in it it's truthy) but we've done the following elsewhere in the codebase so I think for consistency sake it's worthwhile adopting it

 const setRefs = (node: HTMLInputElement | null) => {
      internalRef.current = node;
      if (typeof ref === "function") {
        ref(node);
      } else if (ref) {
        (ref as React.MutableRefObject<HTMLInputElement | null>).current = node;
      }
    };
  
    const stateProps = { disabled, readOnly };

    useEffect(() => {
      if (autoFocus) {
        internalRef.current?.focus();
      }
    }, [autoFocus]);

Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (non-blocking): we could actually make this a custom (internal) hook so it's DRY and reuse the pattern where needed

// useCombinedRefs

import {MutableRefObject, RefCallback} from "react";

export default function combineRefs<T = unknown>(
  ...refs: (MutableRefObject<T | null> | RefCallback<T> | null | undefined)[]
): RefCallback<T> {
  return (node: T | null) => {
    refs.forEach((ref) => {
      if (!ref) return;

      if (typeof ref === "function") {
        ref(node);
      } else {
        ref.current = node;
      }
    });
  };
};
// input.component
 
...

const combinedRef = useCombinedRefs(internalRef, ref);

...

useEffect(() => {
  if (autoFocus) {
      internalRef.current?.focus();
   }
}, [autoFocus]);

...

<input type="text" ref={combinedRef} {...stateProps} {...props} />

Copy link
Contributor

Choose a reason for hiding this comment

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

comment (non-blocking): if this is the same for all components I wonder if it's more appropriate in the shared _internal_ directory (still as a _next_)

Comment on lines +4 to +5

interface StyledTextInputProps {
Copy link
Contributor

Choose a reason for hiding this comment

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

interface StyledTextInputProps {
$labelInline?: boolean;
$containerWidth?: number;
$size?: "small" | "medium" | "large";
}

only size is needed really rest if up to you

labelSetWidth?: number;
}

interface InputSetProps {
Copy link
Contributor

Choose a reason for hiding this comment

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

interface InputSetProps {
$inputWidth?: number;
$size?: "small" | "medium" | "large";
}

Fill in all fields marked with
</RequiredFieldsIndicator>
<Textbox
<TextInput
Copy link
Contributor

Choose a reason for hiding this comment

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

comment: we need to consider what we need to still capture for the old textbox visual regression wise, again worth discussing with the rest of the team in slack

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed - maybe use one TextInput and one Textbox to cover off both?

Fill in all fields marked with
</RequiredFieldsIndicator>
<Textbox
<TextInput
Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed - maybe use one TextInput and one Textbox to cover off both?

Comment on lines +32 to +33
font-weight: ${isError ? "500" : "400"};
line-height: 150%;
Copy link
Contributor

Choose a reason for hiding this comment

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

question: would these need to be tokens, too? Or are we leaving those aside for now?

Copy link
Contributor

Choose a reason for hiding this comment

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

question: would these need to be tokens, too? Or are we leaving those aside for now?

@damienrobson-sage we don't currently distribute core tokens like line-height or font-weight. instead Font tokens can be applied which contain those core values.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, this is due to a little misunderstanding on my part, some hard coded font-familys, weights, colours, sizes etc. have been used instead of the font shorthand with the corresponding font token. I'll make sure this is addressed where i've used them 👍

import StyledHintText from "./hint-text.style";

export interface HintTextProps {
/** Children elements */
Copy link
Contributor

Choose a reason for hiding this comment

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

nitpick: Child elements or Children 😆

Comment on lines +34 to +35
/** The width of the input as a percentage (e.g., 50 for 50%) */
inputWidth?: number;
Copy link
Contributor

Choose a reason for hiding this comment

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

question: just to confirm, definitely only percentage values to be supported?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The DS seem to point toward using percentages for widths on the component, I feel like it keeps things consistent and adaptive. Also gives us more granular control, opposed to supporting any string values etc.

Copy link
Contributor

Choose a reason for hiding this comment

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

Perfect, just double-checking given some components support "any valid CSS values" 👍

);
};

export default React.memo(Label);
Copy link
Contributor

Choose a reason for hiding this comment

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

question: what's the reason for the manual memoisation?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The old internal label component was memorized, so I followed suit here, after thinking about it, I don't think it's really needed, so I will remove 👍

@tomdavies73 tomdavies73 changed the title feat(textbox): align with DS - WIP feat(text-input): add new next component Nov 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

4 participants