Skip to content

Fix FormControl aria-describedby attribute handling for improved accessibility#133

Closed
codegen-sh[bot] wants to merge 2 commits into
mainfrom
codegen-bot/fix-form-control-aria-attributes
Closed

Fix FormControl aria-describedby attribute handling for improved accessibility#133
codegen-sh[bot] wants to merge 2 commits into
mainfrom
codegen-bot/fix-form-control-aria-attributes

Conversation

@codegen-sh
Copy link
Copy Markdown
Contributor

@codegen-sh codegen-sh Bot commented Aug 20, 2025

Description

This PR fixes an issue with the FormControl component's handling of the aria-describedby attribute. The previous implementation always set the aria-describedby attribute to the description ID, even when there was no description. This could lead to invalid ARIA references.

Changes

  • Modified the FormControl component to only include the aria-describedby attribute when there's an actual description or error message
  • Improved the logic to handle both description and error message IDs properly
  • Used array-based approach for cleaner code and better maintainability

Testing

  • Built the package successfully
  • Type-checked the code to ensure no TypeScript errors
  • Verified the fix works with the Select component and other form components

This is a patch release (v0.19.5) that improves accessibility for all form components that use the FormControl component, including the Select component.

Summary by CodeRabbit

  • New Features
    • No user-facing features added.
  • Bug Fixes
    • Improved form accessibility: aria-describedby is now only set when valid, avoiding undefined values and ensuring accurate screen reader descriptions.
  • Chores
    • Bumped components package version to 0.19.5.
    • Reformatted workspace configuration for consistency with no behavioral changes.

@bolt-new-by-stackblitz
Copy link
Copy Markdown

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@github-actions
Copy link
Copy Markdown
Contributor

📝 Storybook Preview: View Storybook

This preview will be updated automatically when you push new changes to this PR.

Note: The preview will be available after the workflow completes and the PR is approved for deployment.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 20, 2025

Walkthrough

Root package.json reformatted the workspaces array to multi-line without content changes. packages/components/package.json version bumped from 0.19.4 to 0.19.5. In form.tsx, aria-describedby computation changed to an array-based approach, conditionally applying the attribute only when IDs exist and including the message ID only when error is present.

Changes

Cohort / File(s) Summary
Workspace config formatting
package.json
Reformatted workspaces from inline to multi-line array; entries unchanged ("apps/*", "packages/*").
Package metadata bump
packages/components/package.json
Version updated: 0.19.4 → 0.19.5; no other fields changed.
Form control ARIA handling
packages/components/src/ui/form.tsx
Switched aria-describedby construction to array, conditionally adds description and error message IDs; only sets attribute when non-empty. No public API changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant F as FormControl (form.tsx)
  participant A as Accessibility IDs
  participant D as DOM Props

  Note over F: Render cycle
  F->>A: Compute descriptionId
  F->>A: Compute messageId (error state may apply)
  A-->>F: IDs (descriptionId?, messageId?)

  F->>F: Build ariaDescribedBy = []
  alt descriptionId exists
    F->>F: Push descriptionId
  end
  alt error is true and messageId exists
    F->>F: Push messageId
  end

  alt ariaDescribedBy has entries
    F->>D: Set aria-describedby = joined IDs
  else
    F->>D: Omit aria-describedby
  end
  F->>D: Set aria-invalid based on error
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

I twitch my ears at lists of IDs,
Hop-hop, I tidy ARIA’s breeze.
No “undefined” in fields today—
Just tidy trails to guide the way.
A version bump, a gentle cheer,
Carrots aligned, the path is clear. 🥕🐇

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codegen-bot/fix-form-control-aria-attributes

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
packages/components/src/ui/form.tsx (2)

108-116: Good fix to conditionally apply aria-describedby; suggest merging user-provided tokens and tightening types.

Two improvements:

  • Preserve any existing aria-describedby from the consumer instead of overwriting it.
  • Explicitly type the array for clarity and dedupe tokens to avoid repeated IDs.

Apply this diff:

-  // Fix: Only include aria-describedby if there's an error or description
-  const ariaDescribedBy = [];
-  if (computedDescriptionId) ariaDescribedBy.push(computedDescriptionId);
-  if (error && computedMessageId) ariaDescribedBy.push(computedMessageId);
+  // Fix: Only include aria-describedby when relevant IDs exist; merge any user-provided tokens
+  const ariaDescribedBy: string[] = [];
+  const userDescribedBy = (restProps as any)['aria-describedby'] as string | undefined;
+  if (userDescribedBy) ariaDescribedBy.push(...userDescribedBy.trim().split(/\s+/));
+  if (fromPropsDesc) ariaDescribedBy.push(computedDescriptionId);
+  if (error && computedMessageId) ariaDescribedBy.push(computedMessageId);
@@
-    ...(ariaDescribedBy.length > 0 ? { 'aria-describedby': ariaDescribedBy.join(' ') } : {}),
+    ...(ariaDescribedBy.length > 0
+      ? { 'aria-describedby': Array.from(new Set(ariaDescribedBy)).join(' ') }
+      : {}),

Notes:

  • Using fromPropsDesc instead of computedDescriptionId avoids assuming a description exists merely because the context can supply an ID.
  • De-duping prevents duplicates when users include your IDs manually.

108-116: Double-check: presence of IDs ≠ presence of DOM nodes.

Even with this change, computedDescriptionId being truthy doesn’t guarantee a is rendered. If consumers don’t mount FormDescription, aria-describedby may still point to a non-existent node.

Options:

  • Minimal: gate inclusion on fromPropsDesc only (as in the diff above), since it signals explicit intent from the caller.
  • Robust: have FormDescription/FormMessage register their presence in context on mount/unmount (e.g., maintain a Set of described-by IDs). FormControl then derives the final list from that registry.

If you want, I can draft a lightweight presence-registration approach that doesn’t change the public API.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between df67a98 and 7181659.

📒 Files selected for processing (3)
  • package.json (1 hunks)
  • packages/components/package.json (1 hunks)
  • packages/components/src/ui/form.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (12)
{package.json,packages/**/package.json,apps/**/package.json}

📄 CodeRabbit Inference Engine (.cursor/rules/monorepo-organization.mdc)

Use consistent versioning across packages

Files:

  • packages/components/package.json
  • package.json
{packages/**/package.json,apps/**/package.json}

📄 CodeRabbit Inference Engine (.cursor/rules/monorepo-organization.mdc)

Define peerDependencies, dependencies, and devDependencies appropriately in package.json

Files:

  • packages/components/package.json
packages/components/src/ui/**/*.tsx

📄 CodeRabbit Inference Engine (.cursor/rules/form-component-patterns.mdc)

packages/components/src/ui/**/*.tsx: Build on @radix-ui components as the foundation for base UI components
Follow the component composition pattern for UI components that accept form integration

Files:

  • packages/components/src/ui/form.tsx
packages/components/src/ui/*.{tsx,ts}

📄 CodeRabbit Inference Engine (.cursor/rules/form-component-patterns.mdc)

Base UI components should be named as ComponentName in ui/ directory

Files:

  • packages/components/src/ui/form.tsx
**/*.{tsx,ts}

📄 CodeRabbit Inference Engine (.cursor/rules/form-component-patterns.mdc)

**/*.{tsx,ts}: Props interfaces should be named as ComponentNameProps
Form schemas should be named formSchema or componentNameSchema

Files:

  • packages/components/src/ui/form.tsx
packages/components/src/{remix-hook-form,ui}/*.{tsx,ts}

📄 CodeRabbit Inference Engine (.cursor/rules/form-component-patterns.mdc)

Always export both the component and its props type

Files:

  • packages/components/src/ui/form.tsx
{apps,packages}/**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/monorepo-organization.mdc)

{apps,packages}/**/*.{ts,tsx}: Use package name imports for published packages (e.g., import { TextField } from '@lambdacurry/forms/remix-hook-form')
Import from specific entry points (e.g., import { TextField } from '@lambdacurry/forms/remix-hook-form/text-field')
Do not use relative imports across packages (e.g., avoid import { TextField } from '../../packages/components/src/remix-hook-form/text-field')
Order imports: 1) external libraries, 2) internal package imports, 3) cross-package imports, 4) type-only imports (grouped separately)

Files:

  • packages/components/src/ui/form.tsx
{apps,packages}/**/src/**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/monorepo-organization.mdc)

{apps,packages}/**/src/**/*.{ts,tsx}: Use relative imports within the same package (e.g., import { FormControl } from './form')
Use relative imports for sibling directories (e.g., import { Button } from '../ui/button')

Files:

  • packages/components/src/ui/form.tsx
packages/components/src/**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/monorepo-organization.mdc)

packages/components/src/**/*.{ts,tsx}: Always export both component and props type (e.g., export { ComponentName }; export type { ComponentNameProps };)
Use named exports for components for better tree-shaking (e.g., export const ComponentName = ...; avoid default exports)
Avoid default exports for components
Use tree-shaking friendly exports

Files:

  • packages/components/src/ui/form.tsx
{apps,packages}/**/src/**/*.{tsx,ts}

📄 CodeRabbit Inference Engine (.cursor/rules/monorepo-organization.mdc)

{apps,packages}/**/src/**/*.{tsx,ts}: Use kebab-case for component file names (e.g., text-field.tsx, data-table.tsx)
Match component name in PascalCase to the exported component from the file (e.g., text-field.tsx exports TextField)

Files:

  • packages/components/src/ui/form.tsx
packages/components/src/ui/**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/monorepo-organization.mdc)

Don't import from remix-hook-form package in ui components

Files:

  • packages/components/src/ui/form.tsx
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/monorepo-organization.mdc)

Avoid circular dependencies

Files:

  • packages/components/src/ui/form.tsx
🔇 Additional comments (2)
packages/components/package.json (1)

3-3: Version bump to 0.19.5 looks good and aligns with a patch release.

No other changes in this file; publishing metadata remains intact.

package.json (1)

5-8: Workspace formatting change is benign and improves readability.

No behavioral impact. Monorepo globs remain the same.

@jaruesink jaruesink closed this Aug 20, 2025
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.

1 participant