Skip to content

refactor: replace old Checkbox component and Added new TextInput, MaskedInput, and Select components#8214

Open
abhishek-bruno wants to merge 2 commits into
usebruno:mainfrom
abhishek-bruno:feat/form-ui-components
Open

refactor: replace old Checkbox component and Added new TextInput, MaskedInput, and Select components#8214
abhishek-bruno wants to merge 2 commits into
usebruno:mainfrom
abhishek-bruno:feat/form-ui-components

Conversation

@abhishek-bruno

@abhishek-bruno abhishek-bruno commented Jun 9, 2026

Copy link
Copy Markdown
Member

Description

JIRA

Old PR for reference

  • Deleted the old Checkbox and StyledWrapper components from the components directory.
  • Introduced a new Checkbox component with enhanced features including indeterminate state and customizable icons.
  • Added a new StyledWrapper for the Checkbox to manage styles and layout.
  • Created InputWrapper and StyledWrapper components for consistent form field handling.
  • Added new TextInput, MaskedInput, and Select components with their respective styles for improved UI consistency.

Contribution Checklist:

  • I've used AI significantly to create this pull request
  • The pull request only addresses one issue or adds one feature.
  • The pull request does not introduce any breaking changes
  • I have added screenshots or gifs to help explain the change if applicable.
  • I have read the contribution guidelines.
  • Create an issue and link to the pull request.

Note: Keeping the PR small and focused helps make it easier to review and merge. If you have multiple changes you want to make, please consider submitting them as separate pull requests.

Publishing to New Package Managers

Please see here for more information.

…kedInput, and Select components

- Deleted the old Checkbox and StyledWrapper components from the components directory.
- Introduced a new Checkbox component with enhanced features including indeterminate state and customizable icons.
- Added a new StyledWrapper for the Checkbox to manage styles and layout.
- Created InputWrapper and StyledWrapper components for consistent form field handling.
- Added new TextInput, MaskedInput, and Select components with their respective styles for improved UI consistency.
…accessibility improvements

- Added useId for unique ID generation in Checkbox and Select components, improving accessibility.
- Updated Checkbox to use generated IDs for aria attributes and labels.
- Refactored InputWrapper to accept and apply generated IDs for label, description, and error elements.
- Modified Select component to include aria attributes for better screen reader support.
- Adjusted StyledWrapper imports to reference constants for consistent styling across components.
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Modernizes form inputs by introducing a new UI component library with InputWrapper infrastructure, TextInput, Checkbox, MaskedInput, and Select dropdown. The legacy Checkbox in components/ is removed. Each component supports controlled/uncontrolled patterns, theming, and accessibility attributes. ActionIcon theme color lookup is updated.

Changes

Form Components Library

Layer / File(s) Summary
InputWrapper infrastructure and constants
src/ui/InputWrapper/constants.js, src/ui/InputWrapper/StyledWrapper.js, src/ui/InputWrapper/index.js
Establishes shared INPUT_SIZES tokens (sm/md padding, font-size, border-radius, label-font-size) and InputWrapper component that wraps inputs with optional label/description/error, html-for linking, and required asterisk. All downstream inputs use this base.
TextInput component
src/ui/TextInput/StyledWrapper.js, src/ui/TextInput/index.js
Controlled text input with focus/error/disabled state tracking, left/right section slots, and dynamic sizing from INPUT_SIZES.
Checkbox component
src/ui/Checkbox/StyledWrapper.js, src/ui/Checkbox/index.js
Checkbox with controlled/uncontrolled and indeterminate states, label positioning, aria-labelledby/describedby, and custom/default check/minus icons.
MaskedInput (password toggle)
src/ui/MaskedInput/StyledWrapper.js, src/ui/MaskedInput/index.js
Password input with controlled/uncontrolled eye-icon visibility toggle, focus tracking, and left/right section support.
Select dropdown
src/ui/Select/StyledWrapper.js, src/ui/Select/index.js
Dropdown with optional search filtering, keyboard navigation (arrows/home/end/escape), clear/loading states, custom option rendering, and Popper modifier to sync dropdown width with trigger.
ActionIcon theme update
src/ui/ActionIcon/StyledWrapper.js
Hover color now resolves from theme.colors.text[colorOnHover] with fallback to raw value.

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested Reviewers

  • helloanoop
  • lohit-bruno
  • bijin-bruno
  • naman-bruno

Poem

📦 New form inputs rise, a library born,
TextInput, Checkbox, MaskedInput—all reborn,
Select dropdown spins with search and flow,
InputWrapper threads below, a steady grow.
Legacy checkbox fades, the new era blooms ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main changes: removing the old Checkbox component and adding new form UI components (TextInput, MaskedInput, Select).
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 4

🧹 Nitpick comments (2)
packages/bruno-app/src/ui/Checkbox/index.js (1)

66-71: 💤 Low value

Optional: Simplify click handling pattern.

The current implementation uses a custom click handler on the wrapper that programmatically clicks the input, while also stopping propagation on both the wrapper and input. This is more complex than needed.

Consider using a native <label> element wrapping the input, which provides built-in click-to-focus behavior without custom handlers or stopPropagation.

♻️ Simplified alternative
   return (
     <StyledWrapper
+      as="label"
+      htmlFor={inputId}
       className={className}
       $size={size}
       $disabled={disabled}
       $labelPosition={labelPosition}
       $color={color}
       $iconColor={iconColor}
       $radius={radius}
-      onClick={handleClick}
     >
       <div className="checkbox-box">
         <input
           ref={inputRef}
           type="checkbox"
           className={`checkbox-input ${indeterminate ? 'checkbox-indeterminate' : ''}`}
           id={inputId}
           name={name}
           data-testid={testId}
           value={value}
           checked={checked}
           defaultChecked={defaultChecked}
           disabled={disabled}
           onChange={onChange}
           aria-labelledby={labelId}
           aria-describedby={describedBy}
-          onClick={(e) => e.stopPropagation()}
         />
         <span className="checkbox-icon">{checkedIcon}</span>
       </div>
       {/* ... label content ... */}
     </StyledWrapper>
   );

Also applies to: 88-88, 105-105

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-app/src/ui/Checkbox/index.js` around lines 66 - 71, The
wrapper's custom click handler (handleClick) and stopPropagation logic is
unnecessary—replace the clickable wrapper element with a native <label> that
wraps the input (using inputRef or the input's id) so browser-built
click-to-toggle behavior handles focus/clicks, remove handleClick, the manual
inputRef.current.click() call, and any stopPropagation on the input/wrapper;
ensure the Checkbox component still respects the disabled prop and remove the
redundant handlers at the referenced locations (handleClick, inputRef usage, and
the stopPropagation calls).
packages/bruno-app/src/ui/Select/StyledWrapper.js (1)

13-31: 💤 Low value

Consider extracting shared wrapper styles.

The base wrapper styles (padding, font-size, border-radius, focus/error/disabled states) are duplicated across TextInput (lines 5-25), MaskedInput (lines 5-25), and Select (lines 13-31). Per coding guidelines, abstractions are recommended at 3+ occurrences—you're exactly at that threshold.

However, each component uses different class names (.text-input-wrapper, .masked-input-wrapper, .select-trigger) and has unique child elements, so extracting a shared CSS mixin might not provide significant value. Consider this if you add a 4th input component.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-app/src/ui/Select/StyledWrapper.js` around lines 13 - 31, The
shared base wrapper styles (padding, font-size, border-radius, disabled/focus
states) are duplicated across TextInput, MaskedInput and Select
(.text-input-wrapper, .masked-input-wrapper, .select-trigger) — extract those
common rules into a single reusable style/mixin (e.g., inputWrapperStyles or a
styled BaseInputWrapper) that uses INPUT_SIZES and theme lookups, then replace
the duplicated blocks in StyledWrapper.js and the other input Styled files to
extend or compose that mixin; keep unique class-specific children/structure in
each component and only move the truly shared properties (padding, font-size,
border-radius, .disabled, .select-open / focus/error border handling) into the
shared style to avoid changing class names or child selectors.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/bruno-app/src/ui/Checkbox/index.js`:
- Around line 109-114: The Checkbox component currently renders label,
description, and error inline instead of using the shared InputWrapper; update
the Checkbox render to wrap the checkbox input with InputWrapper and pass the
generated IDs (labelId, descId, errId) along with label, description, and error
props to InputWrapper so it handles those elements consistently with
TextInput/MaskedInput/Select; ensure the checkbox input remains accessible
(aria-labelledby/aria-describedby) and adjust any className/layout props or
InputWrapper usage to preserve the checkbox-specific layout.
- Around line 99-100: The Checkbox component is currently passing both checked
and defaultChecked causing mixed controlled/uncontrolled behavior; update the
render logic in the Checkbox component to only pass defaultChecked when the
checked prop is undefined (uncontrolled mode) and pass checked (and
require/expect onChange) when checked is provided (controlled mode), i.e.,
conditionally include either checked or defaultChecked in the input props based
on whether props.checked is defined; ensure prop usage in the component
(checked, defaultChecked, onChange) aligns with this conditional mode.

In `@packages/bruno-app/src/ui/MaskedInput/index.js`:
- Around line 70-100: MaskedInput is missing accessibility attributes: import
useId, generate unique description and error IDs (e.g., descriptionId, errorId)
inside the MaskedInput component, pass those IDs into InputWrapper (so it can
render aria targets), and add aria-describedby on the input combining the
descriptionId and errorId when present and aria-invalid={!!error} on the
<input>; keep using the existing id prop for htmlFor, and update the input
element in MaskedInput to include aria-describedby and aria-invalid so screen
readers can associate the field with its description and error state.

In `@packages/bruno-app/src/ui/TextInput/index.js`:
- Around line 81-100: The TextInput component's <input> is not exposing
aria-describedby and aria-invalid to connect it with InputWrapper's
description/error nodes; use React's useId inside the TextInput component to
generate stable IDs (e.g. descriptionId and errorId), pass them into
InputWrapper (the same way Select does) and add aria-describedby on the <input>
pointing to the description/error id(s) and aria-invalid={!!error} on the input;
ensure you import useId, generate IDs near the component start, pass the IDs
into InputWrapper props, and set the input attributes (aria-describedby and
aria-invalid) on the input element to wire accessibility properly.

---

Nitpick comments:
In `@packages/bruno-app/src/ui/Checkbox/index.js`:
- Around line 66-71: The wrapper's custom click handler (handleClick) and
stopPropagation logic is unnecessary—replace the clickable wrapper element with
a native <label> that wraps the input (using inputRef or the input's id) so
browser-built click-to-toggle behavior handles focus/clicks, remove handleClick,
the manual inputRef.current.click() call, and any stopPropagation on the
input/wrapper; ensure the Checkbox component still respects the disabled prop
and remove the redundant handlers at the referenced locations (handleClick,
inputRef usage, and the stopPropagation calls).

In `@packages/bruno-app/src/ui/Select/StyledWrapper.js`:
- Around line 13-31: The shared base wrapper styles (padding, font-size,
border-radius, disabled/focus states) are duplicated across TextInput,
MaskedInput and Select (.text-input-wrapper, .masked-input-wrapper,
.select-trigger) — extract those common rules into a single reusable style/mixin
(e.g., inputWrapperStyles or a styled BaseInputWrapper) that uses INPUT_SIZES
and theme lookups, then replace the duplicated blocks in StyledWrapper.js and
the other input Styled files to extend or compose that mixin; keep unique
class-specific children/structure in each component and only move the truly
shared properties (padding, font-size, border-radius, .disabled, .select-open /
focus/error border handling) into the shared style to avoid changing class names
or child selectors.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 10df57eb-d5a0-4acb-b8a4-2216d165b564

📥 Commits

Reviewing files that changed from the base of the PR and between 216d8e7 and c42f5ed.

📒 Files selected for processing (14)
  • packages/bruno-app/src/components/Checkbox/StyledWrapper.js
  • packages/bruno-app/src/components/Checkbox/index.js
  • packages/bruno-app/src/ui/ActionIcon/StyledWrapper.js
  • packages/bruno-app/src/ui/Checkbox/StyledWrapper.js
  • packages/bruno-app/src/ui/Checkbox/index.js
  • packages/bruno-app/src/ui/InputWrapper/StyledWrapper.js
  • packages/bruno-app/src/ui/InputWrapper/constants.js
  • packages/bruno-app/src/ui/InputWrapper/index.js
  • packages/bruno-app/src/ui/MaskedInput/StyledWrapper.js
  • packages/bruno-app/src/ui/MaskedInput/index.js
  • packages/bruno-app/src/ui/Select/StyledWrapper.js
  • packages/bruno-app/src/ui/Select/index.js
  • packages/bruno-app/src/ui/TextInput/StyledWrapper.js
  • packages/bruno-app/src/ui/TextInput/index.js
💤 Files with no reviewable changes (2)
  • packages/bruno-app/src/components/Checkbox/StyledWrapper.js
  • packages/bruno-app/src/components/Checkbox/index.js

Comment on lines +99 to +100
checked={checked}
defaultChecked={defaultChecked}

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.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Critical: Remove mixed controlled/uncontrolled state.

React will issue warnings when both checked and defaultChecked are provided. The component must be either fully controlled (using checked + onChange) or fully uncontrolled (using defaultChecked only).

As per coding guidelines: "Avoid mixed controlled and uncontrolled state in React components. A component is either controlled or uncontrolled."

🔧 Recommended fix

Choose controlled mode when checked is provided:

         <input
           ref={inputRef}
           type="checkbox"
           className={`checkbox-input ${indeterminate ? 'checkbox-indeterminate' : ''}`}
           id={inputId}
           name={name}
           data-testid={testId}
           value={value}
-          checked={checked}
-          defaultChecked={defaultChecked}
+          {...(checked !== undefined ? { checked } : { defaultChecked })}
           disabled={disabled}
           onChange={onChange}
           aria-labelledby={labelId}
           aria-describedby={describedBy}
           onClick={(e) => e.stopPropagation()}
         />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
checked={checked}
defaultChecked={defaultChecked}
{...(checked !== undefined ? { checked } : { defaultChecked })}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-app/src/ui/Checkbox/index.js` around lines 99 - 100, The
Checkbox component is currently passing both checked and defaultChecked causing
mixed controlled/uncontrolled behavior; update the render logic in the Checkbox
component to only pass defaultChecked when the checked prop is undefined
(uncontrolled mode) and pass checked (and require/expect onChange) when checked
is provided (controlled mode), i.e., conditionally include either checked or
defaultChecked in the input props based on whether props.checked is defined;
ensure prop usage in the component (checked, defaultChecked, onChange) aligns
with this conditional mode.

Source: Coding guidelines

Comment on lines +109 to +114
{(label || description || error) && (
<div className="checkbox-label-content">
{label && <span id={labelId} className="checkbox-label">{label}</span>}
{description && <span id={descId} className="checkbox-description">{description}</span>}
{error && <span id={errId} className="checkbox-error">{error}</span>}
</div>

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.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Major: Use InputWrapper for consistency with other form components.

TextInput, MaskedInput, and Select all use the shared InputWrapper component to render label, description, and error content. This Checkbox component bypasses that pattern and renders them inline, creating API and styling inconsistencies across the form component library.

Refactor to wrap the checkbox with InputWrapper and pass label, description, error, and the generated IDs (labelId, descriptionId, errorId) to it, matching the pattern shown in the relevant code snippets from Select.

♻️ Suggested refactor to use InputWrapper
+import InputWrapper from '../InputWrapper';
+
 const Checkbox = ({
   checked,
   onChange,
   // ... other props
 }) => {
   const inputRef = useRef(null);
   const autoId = useId();
   const inputId = id || autoId;
 
+  const labelId = label ? `${inputId}-label` : undefined;
+  const descriptionId = description ? `${inputId}-desc` : undefined;
+  const errorId = error ? `${inputId}-err` : undefined;
-  const labelId = label ? `${inputId}-label` : undefined;
-  const descId = description ? `${inputId}-desc` : undefined;
-  const errId = error ? `${inputId}-err` : undefined;
-  const describedBy = [descId, errId].filter(Boolean).join(' ') || undefined;
+  const describedBy = [descriptionId, errorId].filter(Boolean).join(' ') || undefined;
 
   // ... rest of logic
 
   return (
+    <InputWrapper
+      label={label}
+      description={description}
+      error={error}
+      size={size}
+      labelId={labelId}
+      descriptionId={descriptionId}
+      errorId={errorId}
+    >
       <StyledWrapper
         className={className}
         $size={size}
         $disabled={disabled}
         $labelPosition={labelPosition}
         $color={color}
         $iconColor={iconColor}
         $radius={radius}
         onClick={handleClick}
       >
         <div className="checkbox-box">
           <input
             ref={inputRef}
             type="checkbox"
             className={`checkbox-input ${indeterminate ? 'checkbox-indeterminate' : ''}`}
             id={inputId}
             name={name}
             data-testid={testId}
             value={value}
             checked={checked}
             defaultChecked={defaultChecked}
             disabled={disabled}
             onChange={onChange}
             aria-labelledby={labelId}
             aria-describedby={describedBy}
             onClick={(e) => e.stopPropagation()}
           />
           <span className="checkbox-icon">{checkedIcon}</span>
         </div>
-        {(label || description || error) && (
-          <div className="checkbox-label-content">
-            {label && <span id={labelId} className="checkbox-label">{label}</span>}
-            {description && <span id={descId} className="checkbox-description">{description}</span>}
-            {error && <span id={errId} className="checkbox-error">{error}</span>}
-          </div>
-        )}
       </StyledWrapper>
+    </InputWrapper>
   );
 };

Note: This may require adjustments to InputWrapper to support the checkbox's unique layout requirements (e.g., label positioning).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-app/src/ui/Checkbox/index.js` around lines 109 - 114, The
Checkbox component currently renders label, description, and error inline
instead of using the shared InputWrapper; update the Checkbox render to wrap the
checkbox input with InputWrapper and pass the generated IDs (labelId, descId,
errId) along with label, description, and error props to InputWrapper so it
handles those elements consistently with TextInput/MaskedInput/Select; ensure
the checkbox input remains accessible (aria-labelledby/aria-describedby) and
adjust any className/layout props or InputWrapper usage to preserve the
checkbox-specific layout.

Comment on lines +70 to +100
return (
<InputWrapper label={label} description={description} error={error} htmlFor={id} required={required} size={size} className={className}>
<StyledWrapper $size={size}>
<div className={wrapperClasses}>
{leftSection && <span className="masked-input-left-section">{leftSection}</span>}
<input
id={id}
type={isVisible ? 'text' : 'password'}
name={name || id}
className="masked-input-field"
value={value}
onChange={onChange}
data-testid={testId}
placeholder={placeholder}
disabled={disabled}
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
/>
{!disabled && (
<button type="button" className="masked-input-toggle" onClick={handleToggle} aria-label={isVisible ? 'Hide value' : 'Show value'}>
{isVisible ? <IconEyeOff size={16} strokeWidth={2} /> : <IconEye size={16} strokeWidth={2} />}
</button>
)}
{rightSection && <span className="masked-input-right-section">{rightSection}</span>}
</div>
</StyledWrapper>
</InputWrapper>

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add aria-describedby and aria-invalid for accessibility.

Like TextInput, this input should link to description/error via aria-describedby and signal error state with aria-invalid. Follow the pattern used in Select (lines 92-96, 308).

♿ Proposed fix to add accessibility attributes

After line 45, generate IDs:

 const [isFocused, setIsFocused] = useState(false);
+
+const autoId = useId();
+const labelId = label ? `${autoId}-label` : undefined;
+const descriptionId = description ? `${autoId}-desc` : undefined;
+const errorId = error ? `${autoId}-err` : undefined;
+const describedBy = [descriptionId, errorId].filter(Boolean).join(' ') || undefined;

Import useId in line 1:

-import React, { useState } from 'react';
+import React, { useState, useId } from 'react';

Pass IDs to InputWrapper (line 71):

-<InputWrapper label={label} description={description} error={error} htmlFor={id} required={required} size={size} className={className}>
+<InputWrapper label={label} description={description} error={error} htmlFor={id} required={required} size={size} className={className} labelId={labelId} descriptionId={descriptionId} errorId={errorId}>

Add aria attributes to input (after line 84):

   disabled={disabled}
+  aria-invalid={error ? 'true' : undefined}
+  aria-describedby={describedBy}
   autoComplete="off"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-app/src/ui/MaskedInput/index.js` around lines 70 - 100,
MaskedInput is missing accessibility attributes: import useId, generate unique
description and error IDs (e.g., descriptionId, errorId) inside the MaskedInput
component, pass those IDs into InputWrapper (so it can render aria targets), and
add aria-describedby on the input combining the descriptionId and errorId when
present and aria-invalid={!!error} on the <input>; keep using the existing id
prop for htmlFor, and update the input element in MaskedInput to include
aria-describedby and aria-invalid so screen readers can associate the field with
its description and error state.

Comment on lines +81 to +100
<input
id={id}
type={type}
name={name || id}
className="text-input-field"
value={value}
onChange={onChange}
placeholder={placeholder}
disabled={disabled}
data-testid={testId}
readOnly={readOnly}
autoFocus={autoFocus}
autoComplete={autoComplete}
maxLength={maxLength}
min={min}
max={max}
step={step}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
/>

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add aria-describedby and aria-invalid for accessibility.

The input should link to description/error elements via aria-describedby and signal error state with aria-invalid. Currently, InputWrapper renders description/error but they're not connected to the input.

See how Select (lines 92-96, 308) generates IDs with useId and passes them to both InputWrapper and the trigger element. Apply the same pattern here.

♿ Proposed fix to add accessibility attributes

At the top of the component, after line 56:

 const [isFocused, setIsFocused] = useState(false);
+
+const autoId = useId();
+const labelId = label ? `${autoId}-label` : undefined;
+const descriptionId = description ? `${autoId}-desc` : undefined;
+const errorId = error ? `${autoId}-err` : undefined;
+const describedBy = [descriptionId, errorId].filter(Boolean).join(' ') || undefined;

Import useId in line 1:

-import React, { useState } from 'react';
+import React, { useState, useId } from 'react';

Pass IDs to InputWrapper (line 69):

 <InputWrapper
   label={label}
   description={description}
   error={error}
   htmlFor={id}
   required={required}
   size={size}
   className={className}
+  labelId={labelId}
+  descriptionId={descriptionId}
+  errorId={errorId}
 >

Add aria attributes to input (after line 89):

   disabled={disabled}
   data-testid={testId}
+  aria-invalid={error ? 'true' : undefined}
+  aria-describedby={describedBy}
   readOnly={readOnly}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/bruno-app/src/ui/TextInput/index.js` around lines 81 - 100, The
TextInput component's <input> is not exposing aria-describedby and aria-invalid
to connect it with InputWrapper's description/error nodes; use React's useId
inside the TextInput component to generate stable IDs (e.g. descriptionId and
errorId), pass them into InputWrapper (the same way Select does) and add
aria-describedby on the <input> pointing to the description/error id(s) and
aria-invalid={!!error} on the input; ensure you import useId, generate IDs near
the component start, pass the IDs into InputWrapper props, and set the input
attributes (aria-describedby and aria-invalid) on the input element to wire
accessibility properly.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant