From fb06b9a3faf47c909054990ce1761ad12748fb28 Mon Sep 17 00:00:00 2001 From: paanSinghCoder Date: Mon, 4 May 2026 09:07:33 +0530 Subject: [PATCH 1/3] fix(chip,filter-chip): forward HTML attributes onto root element Chip selectively picked props (`onClick`, `role`, `data-state`, etc.) and did not spread the rest, so consumers couldn't pass `style`, `data-*`, `aria-*`, `id`, mouse events, or any other native span attribute. FilterChip already spread `...props` at runtime but its props interface didn't extend any HTMLAttributes type, so the same attributes were rejected by TypeScript. Both components now extend `ComponentProps<'span' | 'div'>` and forward all unspecified attributes to the root element. Existing prop API is preserved: `ariaLabel` (legacy camelCase) still wins over the native `aria-label`, the `disabled` gate on `onClick` still applies, and `role` still defaults to `status` on Chip. Closes part of #674. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../components/chip/__tests__/chip.test.tsx | 39 +++++++++++++++++++ packages/raystack/components/chip/chip.tsx | 34 ++++++++-------- .../__tests__/filter-chip.test.tsx | 17 ++++++++ .../components/filter-chip/filter-chip.tsx | 9 ++--- 4 files changed, 77 insertions(+), 22 deletions(-) diff --git a/packages/raystack/components/chip/__tests__/chip.test.tsx b/packages/raystack/components/chip/__tests__/chip.test.tsx index a15348866..ed2e8ea8e 100644 --- a/packages/raystack/components/chip/__tests__/chip.test.tsx +++ b/packages/raystack/components/chip/__tests__/chip.test.tsx @@ -227,5 +227,44 @@ describe('Chip', () => { const chip = screen.getByRole('status'); expect(chip).toHaveAttribute('aria-label', 'Override Label'); }); + + it('accepts native aria-label attribute', () => { + render(String Child); + + const chip = screen.getByRole('status'); + expect(chip).toHaveAttribute('aria-label', 'Native Label'); + }); + }); + + describe('Forwarded HTML attributes', () => { + it('forwards arbitrary HTML attributes onto the root span', () => { + render( + + Test Chip + + ); + + const chip = screen.getByTestId('chip-root'); + expect(chip).toHaveAttribute('id', 'my-chip'); + expect(chip).toHaveAttribute('data-state', 'active'); + expect(chip).toHaveAttribute('title', 'Tooltip'); + }); + + it('forwards mouse events from the spread props', () => { + const onMouseEnter = vi.fn(); + render( + + Test Chip + + ); + + fireEvent.mouseEnter(screen.getByTestId('chip-root')); + expect(onMouseEnter).toHaveBeenCalledTimes(1); + }); }); }); diff --git a/packages/raystack/components/chip/chip.tsx b/packages/raystack/components/chip/chip.tsx index 424298a4f..aac9da53e 100644 --- a/packages/raystack/components/chip/chip.tsx +++ b/packages/raystack/components/chip/chip.tsx @@ -1,7 +1,7 @@ 'use client'; import { cva, type VariantProps } from 'class-variance-authority'; -import { ReactNode } from 'react'; +import { ComponentProps, ReactNode } from 'react'; import styles from './chip.module.css'; @@ -27,19 +27,16 @@ const chip = cva(styles.chip, { } }); -type ChipProps = VariantProps & { - trailingIcon?: ReactNode; - leadingIcon?: ReactNode; - isDismissible?: boolean; - children: ReactNode; - className?: string; - onDismiss?: () => void; - onClick?: () => void; - role?: string; - ariaLabel?: string; - disabled?: boolean; - 'data-state'?: string; -}; +type ChipProps = ComponentProps<'span'> & + VariantProps & { + trailingIcon?: ReactNode; + leadingIcon?: ReactNode; + isDismissible?: boolean; + children: ReactNode; + onDismiss?: () => void; + ariaLabel?: string; + disabled?: boolean; + }; export const Chip = ({ variant, @@ -55,7 +52,8 @@ export const Chip = ({ role = 'status', ariaLabel, disabled, - 'data-state': dataState + 'aria-label': ariaLabelAttr, + ...props }: ChipProps) => { const handleDismiss = (e: React.MouseEvent) => { e.stopPropagation(); @@ -64,14 +62,16 @@ export const Chip = ({ return ( {leadingIcon && ( { expect(input).toHaveValue('initial value'); }); }); + + describe('Forwarded HTML attributes', () => { + it('forwards arbitrary HTML attributes onto the root div', () => { + render( + + ); + + const root = screen.getByTestId('filter-root'); + expect(root).toHaveAttribute('id', 'my-filter'); + expect(root).toHaveAttribute('title', 'Tooltip'); + }); + }); }); diff --git a/packages/raystack/components/filter-chip/filter-chip.tsx b/packages/raystack/components/filter-chip/filter-chip.tsx index 1ab910194..03942b760 100644 --- a/packages/raystack/components/filter-chip/filter-chip.tsx +++ b/packages/raystack/components/filter-chip/filter-chip.tsx @@ -2,7 +2,7 @@ import { Cross1Icon } from '@radix-ui/react-icons'; import { cva, cx, VariantProps } from 'class-variance-authority'; -import { ReactElement, ReactNode, useCallback, useState } from 'react'; +import { ComponentProps, ReactElement, useCallback, useState } from 'react'; import { FilterOperation, FilterOperator, @@ -32,13 +32,12 @@ const chip = cva(styles.chip, { } }); -export interface FilterChipProps extends VariantProps { +export interface FilterChipProps + extends ComponentProps<'div'>, + VariantProps { label: string; value?: string; onRemove?: () => void; - className?: string; - ref?: React.RefObject; - children?: ReactNode; columnType?: FilterTypes; options?: FilterSelectOption[]; onValueChange?: (value: any, operation: string) => void; From e3fa2006d5da266c1190cc6c2da83562f6ff31c8 Mon Sep 17 00:00:00 2001 From: paanSinghCoder Date: Mon, 4 May 2026 15:48:09 +0530 Subject: [PATCH 2/3] fix(chip): standardize aria-label prop across components and tests --- apps/www/src/components/playground/chip-examples.tsx | 6 +++--- apps/www/src/content/docs/components/chip/demo.ts | 6 +++--- apps/www/src/content/docs/components/chip/props.ts | 2 +- packages/raystack/components/chip/__tests__/chip.test.tsx | 8 ++++---- packages/raystack/components/chip/chip.tsx | 8 ++------ 5 files changed, 13 insertions(+), 17 deletions(-) diff --git a/apps/www/src/components/playground/chip-examples.tsx b/apps/www/src/components/playground/chip-examples.tsx index 6c29e5ef5..8446356e7 100644 --- a/apps/www/src/components/playground/chip-examples.tsx +++ b/apps/www/src/components/playground/chip-examples.tsx @@ -35,7 +35,7 @@ export function ChipExamples() { alert('dismissed')} - ariaLabel='Dismissible chip' + aria-label='Dismissible chip' > Dismissable Chip @@ -44,7 +44,7 @@ export function ChipExamples() { color='accent' isDismissible onDismiss={() => alert('dismissed')} - ariaLabel='Dismissible chip' + aria-label='Dismissible chip' > Dismissable Chip @@ -53,7 +53,7 @@ export function ChipExamples() { color='accent' isDismissible onDismiss={() => alert('dismissed')} - ariaLabel='Dismissible chip' + aria-label='Dismissible chip' > Dismissable Chip diff --git a/apps/www/src/content/docs/components/chip/demo.ts b/apps/www/src/content/docs/components/chip/demo.ts index 6591c9706..e15705ceb 100644 --- a/apps/www/src/content/docs/components/chip/demo.ts +++ b/apps/www/src/content/docs/components/chip/demo.ts @@ -73,9 +73,9 @@ export const dismissableDemo = { type: 'code', code: ` - alert('dismissed')} ariaLabel="Dismissible chip">Dismissable Chip - alert('dismissed')} ariaLabel="Dismissible chip">Dismissable Chip - alert('dismissed')} ariaLabel="Dismissible chip">Dismissable Chip + alert('dismissed')} aria-label="Dismissible chip">Dismissable Chip + alert('dismissed')} aria-label="Dismissible chip">Dismissable Chip + alert('dismissed')} aria-label="Dismissible chip">Dismissable Chip ` }; diff --git a/apps/www/src/content/docs/components/chip/props.ts b/apps/www/src/content/docs/components/chip/props.ts index 99396d0d7..9f270cd68 100644 --- a/apps/www/src/content/docs/components/chip/props.ts +++ b/apps/www/src/content/docs/components/chip/props.ts @@ -44,5 +44,5 @@ export interface ChipProps { role?: string; /** Custom accessibility label for the chip */ - ariaLabel?: string; + 'aria-label'?: string; } diff --git a/packages/raystack/components/chip/__tests__/chip.test.tsx b/packages/raystack/components/chip/__tests__/chip.test.tsx index ed2e8ea8e..ad5b58385 100644 --- a/packages/raystack/components/chip/__tests__/chip.test.tsx +++ b/packages/raystack/components/chip/__tests__/chip.test.tsx @@ -214,15 +214,15 @@ describe('Chip', () => { expect(chip).toHaveAttribute('aria-label', 'Test Label'); }); - it('uses custom ariaLabel when provided', () => { - render(Test Chip); + it('uses custom aria-label when provided', () => { + render(Test Chip); const chip = screen.getByRole('status'); expect(chip).toHaveAttribute('aria-label', 'Custom Label'); }); - it('custom ariaLabel overrides string children', () => { - render(String Child); + it('custom aria-label overrides string children', () => { + render(String Child); const chip = screen.getByRole('status'); expect(chip).toHaveAttribute('aria-label', 'Override Label'); diff --git a/packages/raystack/components/chip/chip.tsx b/packages/raystack/components/chip/chip.tsx index aac9da53e..ccd3518ad 100644 --- a/packages/raystack/components/chip/chip.tsx +++ b/packages/raystack/components/chip/chip.tsx @@ -34,7 +34,6 @@ type ChipProps = ComponentProps<'span'> & isDismissible?: boolean; children: ReactNode; onDismiss?: () => void; - ariaLabel?: string; disabled?: boolean; }; @@ -50,9 +49,8 @@ export const Chip = ({ onDismiss, onClick, role = 'status', - ariaLabel, disabled, - 'aria-label': ariaLabelAttr, + 'aria-label': ariaLabel, ...props }: ChipProps) => { const handleDismiss = (e: React.MouseEvent) => { @@ -66,9 +64,7 @@ export const Chip = ({ className={chip({ variant, size, color, className })} role={role} aria-label={ - ariaLabel ?? - ariaLabelAttr ?? - (typeof children === 'string' ? children : undefined) + ariaLabel ?? (typeof children === 'string' ? children : undefined) } onClick={disabled ? undefined : onClick} data-disabled={disabled || undefined} From c14a51e3e54c5908464b83db9d83707f7908a32e Mon Sep 17 00:00:00 2001 From: paanSinghCoder Date: Mon, 4 May 2026 15:52:33 +0530 Subject: [PATCH 3/3] fix(chip): update Chip component to use standard aria-label and forward HTML attributes --- docs/V1-migration.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/V1-migration.md b/docs/V1-migration.md index 40996ba86..903b1f31c 100644 --- a/docs/V1-migration.md +++ b/docs/V1-migration.md @@ -21,6 +21,7 @@ This guide covers all breaking changes when upgrading from the last stable Radix - [Avatar](#avatar) - [Breadcrumb](#breadcrumb) - [Button](#button) + - [Chip](#chip) - [Checkbox](#checkbox) - [New: `Checkbox.Group`](#new-checkboxgroup) - [New Features](#new-features) @@ -418,6 +419,31 @@ Unchanged: `size`, `radius`, `variant`, `color`, `fallback`, `src`, `alt`, `clas --- +### Chip + +**`ariaLabel` prop removed** -- use the standard `aria-label` HTML attribute: + +```tsx +// Before + + Tag + + +// After + + Tag + +``` + +`Chip` now forwards all standard HTML attributes through `...props`, making the dedicated `ariaLabel` prop redundant. The auto-fallback to string children when no label is supplied is unchanged: + +```tsx +// Still works — uses "Tag" as the accessibility label +Tag +``` + +--- + ### Checkbox 1. **Indeterminate API changed** -- `checked="indeterminate"` replaced by separate `indeterminate` boolean: