@hex-core/components 0.2.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -125,11 +125,21 @@ declare const RadioGroup: React$1.ForwardRefExoticComponent<Omit<RadioGroupPrimi
125
125
  /** A single radio option within a RadioGroup. */
126
126
  declare const RadioGroupItem: React$1.ForwardRefExoticComponent<Omit<RadioGroupPrimitive.RadioGroupItemProps & React$1.RefAttributes<HTMLButtonElement>, "ref"> & React$1.RefAttributes<HTMLButtonElement>>;
127
127
 
128
+ interface SliderProps extends React$1.ComponentPropsWithoutRef<typeof SliderPrimitive.Root> {
129
+ /**
130
+ * Per-thumb accessible labels. When the slider has multiple thumbs, pass
131
+ * one entry per thumb (e.g. ["Minimum", "Maximum"]). For a single-thumb
132
+ * slider, the Root's `aria-label` / `aria-labelledby` is mirrored onto
133
+ * the thumb automatically — pass `thumbLabels` only when those defaults
134
+ * are insufficient.
135
+ */
136
+ thumbLabels?: string[];
137
+ }
128
138
  /**
129
139
  * A range input with one or more draggable thumbs.
130
140
  * Built on Radix UI Slider with keyboard controls (arrows, Home, End, PageUp/Down).
131
141
  */
132
- declare const Slider: React$1.ForwardRefExoticComponent<Omit<SliderPrimitive.SliderProps & React$1.RefAttributes<HTMLSpanElement>, "ref"> & React$1.RefAttributes<HTMLSpanElement>>;
142
+ declare const Slider: React$1.ForwardRefExoticComponent<SliderProps & React$1.RefAttributes<HTMLSpanElement>>;
133
143
 
134
144
  declare const toggleVariants: (props?: ({
135
145
  variant?: "default" | "outline" | null | undefined;
@@ -175,8 +185,17 @@ declare function Skeleton({ className, ...props }: React$1.HTMLAttributes<HTMLDi
175
185
  */
176
186
  declare const Progress: React$1.ForwardRefExoticComponent<Omit<ProgressPrimitive.ProgressProps & React$1.RefAttributes<HTMLDivElement>, "ref"> & React$1.RefAttributes<HTMLDivElement>>;
177
187
 
188
+ interface ScrollAreaProps extends React$1.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root> {
189
+ /**
190
+ * tabIndex applied to the scroll viewport so keyboard users can scroll
191
+ * without a pointer. Defaults to `0` (focusable) — pass `-1` to skip the
192
+ * viewport in the tab order when ScrollArea wraps purely decorative or
193
+ * already-keyboard-reachable content.
194
+ */
195
+ viewportTabIndex?: number;
196
+ }
178
197
  /** A scrollable area with custom-styled scrollbars. Content must be explicitly sized. */
179
- declare const ScrollArea: React$1.ForwardRefExoticComponent<Omit<ScrollAreaPrimitive.ScrollAreaProps & React$1.RefAttributes<HTMLDivElement>, "ref"> & React$1.RefAttributes<HTMLDivElement>>;
198
+ declare const ScrollArea: React$1.ForwardRefExoticComponent<ScrollAreaProps & React$1.RefAttributes<HTMLDivElement>>;
180
199
  /** Styled scrollbar track + thumb. Rendered inside ScrollArea automatically. */
181
200
  declare const ScrollBar: React$1.ForwardRefExoticComponent<Omit<ScrollAreaPrimitive.ScrollAreaScrollbarProps & React$1.RefAttributes<HTMLDivElement>, "ref"> & React$1.RefAttributes<HTMLDivElement>>;
182
201
 
@@ -224,8 +243,22 @@ declare const DialogPortal: React$1.FC<DialogPrimitive.DialogPortalProps>;
224
243
  declare const DialogClose: React$1.ForwardRefExoticComponent<DialogPrimitive.DialogCloseProps & React$1.RefAttributes<HTMLButtonElement>>;
225
244
  /** Dimmed backdrop rendered behind the dialog content. */
226
245
  declare const DialogOverlay: React$1.ForwardRefExoticComponent<Omit<DialogPrimitive.DialogOverlayProps & React$1.RefAttributes<HTMLDivElement>, "ref"> & React$1.RefAttributes<HTMLDivElement>>;
246
+ interface DialogContentProps extends React$1.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {
247
+ /**
248
+ * When `true` (the default), DialogContent caps its height at viewport-2rem
249
+ * and renders children inside a padded inner scroll container. The Close
250
+ * button stays anchored to the (non-scrolling) outer panel so it remains
251
+ * visible even when the user scrolls long content.
252
+ *
253
+ * Pass `scrollable={false}` to opt out — useful when the consumer manages
254
+ * its own scroll surface (e.g. CommandDialog defers scroll to cmdk's
255
+ * internal CommandList).
256
+ */
257
+ scrollable?: boolean;
258
+ }
227
259
  /** The dialog content panel, centered on the overlay. Includes a close button by default. */
228
- declare const DialogContent: React$1.ForwardRefExoticComponent<Omit<DialogPrimitive.DialogContentProps & React$1.RefAttributes<HTMLDivElement>, "ref"> & React$1.RefAttributes<HTMLDivElement>>;
260
+ declare const DialogContent: React$1.ForwardRefExoticComponent<DialogContentProps & React$1.RefAttributes<HTMLDivElement>>;
261
+
229
262
  /**
230
263
  * Header container inside DialogContent; stacks title and description.
231
264
  * @returns A div wrapping title/description with vertical rhythm
@@ -511,7 +544,11 @@ declare const TableRow: React$1.ForwardRefExoticComponent<React$1.HTMLAttributes
511
544
  declare const TableHead: React$1.ForwardRefExoticComponent<React$1.ThHTMLAttributes<HTMLTableCellElement> & React$1.RefAttributes<HTMLTableCellElement>>;
512
545
  /** `<td>` with consistent padding. */
513
546
  declare const TableCell: React$1.ForwardRefExoticComponent<React$1.TdHTMLAttributes<HTMLTableCellElement> & React$1.RefAttributes<HTMLTableCellElement>>;
514
- /** `<caption>` positioned below the table. */
547
+ /**
548
+ * Visible `<caption>` rendered below the table. The parent `<Table>` sets
549
+ * `caption-bottom`, so the caption is announced first by screen readers when
550
+ * entering the table, then visually placed below the rows.
551
+ */
515
552
  declare const TableCaption: React$1.ForwardRefExoticComponent<React$1.HTMLAttributes<HTMLTableCaptionElement> & React$1.RefAttributes<HTMLTableCaptionElement>>;
516
553
 
517
554
  /**
@@ -523,13 +560,24 @@ declare const TableCaption: React$1.ForwardRefExoticComponent<React$1.HTMLAttrib
523
560
  interface DataTableProps<TData> {
524
561
  columns: ColumnDef<TData, unknown>[];
525
562
  data: TData[];
563
+ /**
564
+ * Visible caption rendered below the table. Announced by screen readers
565
+ * when the user enters the table. Provide either `caption` or `aria-label`.
566
+ */
567
+ caption?: React$1.ReactNode;
568
+ /**
569
+ * Accessible label for the table when no visible caption is shown.
570
+ * Forwarded as `aria-label` on the underlying `<table>` element. Kebab-case
571
+ * to match the canonical ARIA prop convention used elsewhere in Hex UI.
572
+ */
573
+ "aria-label"?: string;
526
574
  }
527
575
  /**
528
576
  * Render a data-driven table from TanStack column definitions.
529
- * @param props - Columns and data
577
+ * @param props - Columns, data, and optional accessible labelling (`caption` or `aria-label`)
530
578
  * @returns A styled Table rendered from the TanStack row model
531
579
  */
532
- declare function DataTable<TData>({ columns, data }: DataTableProps<TData>): react_jsx_runtime.JSX.Element;
580
+ declare function DataTable<TData>({ columns, data, caption, "aria-label": ariaLabel, }: DataTableProps<TData>): react_jsx_runtime.JSX.Element;
533
581
 
534
582
  /**
535
583
  * Root nav landmark for pagination controls.
@@ -690,14 +738,17 @@ declare const CommandGroup: React$1.ForwardRefExoticComponent<Omit<{
690
738
  value?: string;
691
739
  forceMount?: boolean;
692
740
  } & React$1.RefAttributes<HTMLDivElement>, "ref"> & React$1.RefAttributes<HTMLDivElement>>;
693
- /** Horizontal rule between groups. */
694
- declare const CommandSeparator: React$1.ForwardRefExoticComponent<Omit<Pick<Pick<React$1.DetailedHTMLProps<React$1.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "key" | keyof React$1.HTMLAttributes<HTMLDivElement>> & {
695
- ref?: React$1.Ref<HTMLDivElement>;
696
- } & {
697
- asChild?: boolean;
698
- }, "asChild" | "key" | keyof React$1.HTMLAttributes<HTMLDivElement>> & {
699
- alwaysRender?: boolean;
700
- } & React$1.RefAttributes<HTMLDivElement>, "ref"> & React$1.RefAttributes<HTMLDivElement>>;
741
+ /**
742
+ * Horizontal rule between groups. Renders as a presentational `<div>` (no role)
743
+ * so it can sit inside CommandList (role=listbox) without violating ARIA's
744
+ * required-children rule for listbox. The line is purely decorative — cmdk's
745
+ * built-in Separator hardcodes `role="separator"`, which axe rejects in this
746
+ * context, so we render the divider directly.
747
+ *
748
+ * The `data-cmdk-separator` attribute is preserved so existing CSS / test
749
+ * selectors that target cmdk's separator continue to match.
750
+ */
751
+ declare const CommandSeparator: React$1.ForwardRefExoticComponent<React$1.HTMLAttributes<HTMLDivElement> & React$1.RefAttributes<HTMLDivElement>>;
701
752
  /** Selectable item. onSelect fires on Enter or click. */
702
753
  declare const CommandItem: React$1.ForwardRefExoticComponent<Omit<{
703
754
  children?: React$1.ReactNode;
@@ -746,8 +797,10 @@ interface ComboboxProps {
746
797
  disabled?: boolean;
747
798
  /** Extra class names on the trigger button. */
748
799
  className?: string;
749
- /** Accessible label for the trigger (required when no adjacent visible <label>). */
800
+ /** Accessible label for the trigger (required when no adjacent visible label). */
750
801
  "aria-label"?: string;
802
+ /** Id of an external visible label that names this combobox. */
803
+ "aria-labelledby"?: string;
751
804
  }
752
805
  /**
753
806
  * Searchable select input built on Command + Popover.
@@ -756,7 +809,7 @@ interface ComboboxProps {
756
809
  * the trigger; the popover contains a CommandInput and filtered CommandList.
757
810
  * @returns A trigger button that opens a filtered option list.
758
811
  */
759
- declare function Combobox({ options, value, onChange, placeholder, searchPlaceholder, emptyText, disabled, className, "aria-label": ariaLabel, }: ComboboxProps): react_jsx_runtime.JSX.Element;
812
+ declare function Combobox({ options, value, onChange, placeholder, searchPlaceholder, emptyText, disabled, className, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, }: ComboboxProps): react_jsx_runtime.JSX.Element;
760
813
  declare namespace Combobox {
761
814
  var displayName: string;
762
815
  }
package/dist/index.js CHANGED
@@ -488,30 +488,46 @@ RadioGroupItem.displayName = "RadioGroupItem";
488
488
  import * as SliderPrimitive from "@radix-ui/react-slider";
489
489
  import * as React10 from "react";
490
490
  import { jsx as jsx11, jsxs as jsxs4 } from "react/jsx-runtime";
491
- var Slider = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxs4(
492
- SliderPrimitive.Root,
493
- {
494
- ref,
495
- className: cn("relative flex w-full touch-none select-none items-center", className),
496
- ...props,
497
- children: [
498
- /* @__PURE__ */ jsx11(SliderPrimitive.Track, { className: "relative h-2 w-full grow overflow-hidden rounded-full bg-secondary", children: /* @__PURE__ */ jsx11(SliderPrimitive.Range, { className: "absolute h-full bg-primary" }) }),
499
- (props.value ?? props.defaultValue ?? [0]).map((_, i) => /* @__PURE__ */ jsx11(
500
- SliderPrimitive.Thumb,
501
- {
502
- className: cn(
503
- "block h-5 w-5 rounded-full border-2 border-primary bg-background",
504
- "transition-all duration-[var(--duration-normal,200ms)] ease-out shadow-md",
505
- "hover:shadow-lg hover:scale-110",
506
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
507
- "disabled:pointer-events-none disabled:opacity-50"
508
- )
509
- },
510
- i
511
- ))
512
- ]
491
+ var Slider = React10.forwardRef(({ className, thumbLabels, ...props }, ref) => {
492
+ const values = props.value ?? props.defaultValue ?? [0];
493
+ const rootLabel = props["aria-label"];
494
+ const rootLabelledBy = props["aria-labelledby"];
495
+ if (typeof process !== "undefined" && process.env?.NODE_ENV !== "production" && thumbLabels && thumbLabels.length !== values.length) {
496
+ console.warn(
497
+ `Slider: thumbLabels.length (${thumbLabels.length}) does not match value.length (${values.length}). Missing labels fall back to indexed names; extra labels are ignored.`
498
+ );
513
499
  }
514
- ));
500
+ return /* @__PURE__ */ jsxs4(
501
+ SliderPrimitive.Root,
502
+ {
503
+ ref,
504
+ className: cn("relative flex w-full touch-none select-none items-center", className),
505
+ ...props,
506
+ children: [
507
+ /* @__PURE__ */ jsx11(SliderPrimitive.Track, { className: "relative h-2 w-full grow overflow-hidden rounded-full bg-secondary", children: /* @__PURE__ */ jsx11(SliderPrimitive.Range, { className: "absolute h-full bg-primary" }) }),
508
+ values.map((_, i) => {
509
+ const explicit = thumbLabels?.[i];
510
+ const fallback = values.length === 1 ? rootLabel : rootLabel ? `${rootLabel} (${i + 1} of ${values.length})` : void 0;
511
+ return /* @__PURE__ */ jsx11(
512
+ SliderPrimitive.Thumb,
513
+ {
514
+ "aria-label": explicit ?? fallback,
515
+ "aria-labelledby": explicit || fallback ? void 0 : rootLabelledBy,
516
+ className: cn(
517
+ "block h-5 w-5 rounded-full border-2 border-primary bg-background",
518
+ "transition-all duration-[var(--duration-normal,200ms)] ease-out shadow-md",
519
+ "hover:shadow-lg hover:scale-110",
520
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
521
+ "disabled:pointer-events-none disabled:opacity-50"
522
+ )
523
+ },
524
+ i
525
+ );
526
+ })
527
+ ]
528
+ }
529
+ );
530
+ });
515
531
  Slider.displayName = "Slider";
516
532
 
517
533
  // src/primitives/toggle/toggle.tsx
@@ -666,14 +682,24 @@ Progress.displayName = "Progress";
666
682
  import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
667
683
  import * as React15 from "react";
668
684
  import { jsx as jsx17, jsxs as jsxs5 } from "react/jsx-runtime";
669
- var ScrollArea = React15.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs5(
685
+ var ScrollArea = React15.forwardRef(({ className, children, viewportTabIndex = 0, ...props }, ref) => /* @__PURE__ */ jsxs5(
670
686
  ScrollAreaPrimitive.Root,
671
687
  {
672
688
  ref,
673
689
  className: cn("relative overflow-hidden", className),
674
690
  ...props,
675
691
  children: [
676
- /* @__PURE__ */ jsx17(ScrollAreaPrimitive.Viewport, { className: "h-full w-full rounded-[inherit]", children }),
692
+ /* @__PURE__ */ jsx17(
693
+ ScrollAreaPrimitive.Viewport,
694
+ {
695
+ tabIndex: viewportTabIndex,
696
+ className: cn(
697
+ "h-full w-full rounded-[inherit]",
698
+ viewportTabIndex >= 0 && "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
699
+ ),
700
+ children
701
+ }
702
+ ),
677
703
  /* @__PURE__ */ jsx17(ScrollBar, { orientation: "vertical" }),
678
704
  /* @__PURE__ */ jsx17(ScrollBar, { orientation: "horizontal" }),
679
705
  /* @__PURE__ */ jsx17(ScrollAreaPrimitive.Corner, {})
@@ -884,15 +910,15 @@ var DialogOverlay = React19.forwardRef(({ className, ...props }, ref) => /* @__P
884
910
  }
885
911
  ));
886
912
  DialogOverlay.displayName = "DialogOverlay";
887
- var DialogContent = React19.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs7(DialogPortal, { children: [
913
+ var DialogContent = React19.forwardRef(({ className, children, scrollable = true, ...props }, ref) => /* @__PURE__ */ jsxs7(DialogPortal, { children: [
888
914
  /* @__PURE__ */ jsx21(DialogOverlay, {}),
889
915
  /* @__PURE__ */ jsxs7(
890
916
  DialogPrimitive.Content,
891
917
  {
892
918
  ref,
893
919
  className: cn(
894
- "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-[var(--gap-md,1rem)]",
895
- "border bg-background p-[var(--space-6,1.5rem)] shadow-lg rounded-lg",
920
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%]",
921
+ scrollable ? "max-h-[calc(100vh-2rem)] border bg-background shadow-lg rounded-lg" : "gap-[var(--gap-md,1rem)] border bg-background p-[var(--space-6,1.5rem)] shadow-lg rounded-lg",
896
922
  "duration-[var(--duration-normal,200ms)] data-[state=open]:animate-in data-[state=closed]:animate-out",
897
923
  "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
898
924
  "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
@@ -900,12 +926,12 @@ var DialogContent = React19.forwardRef(({ className, children, ...props }, ref)
900
926
  ),
901
927
  ...props,
902
928
  children: [
903
- children,
929
+ scrollable ? /* @__PURE__ */ jsx21("div", { className: "grid gap-[var(--gap-md,1rem)] overflow-y-auto p-[var(--space-6,1.5rem)]", children }) : children,
904
930
  /* @__PURE__ */ jsxs7(
905
931
  DialogPrimitive.Close,
906
932
  {
907
933
  className: cn(
908
- "absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background",
934
+ "absolute right-4 top-4 z-10 rounded-sm opacity-70 ring-offset-background bg-background/80 backdrop-blur-sm",
909
935
  "transition-all duration-[var(--duration-normal,200ms)] ease-out hover:opacity-100",
910
936
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
911
937
  "disabled:pointer-events-none"
@@ -1962,7 +1988,10 @@ var TableCaption = React31.forwardRef(({ className, ...props }, ref) => /* @__PU
1962
1988
  "caption",
1963
1989
  {
1964
1990
  ref,
1965
- className: cn("mt-[var(--space-4,1rem)] text-sm text-muted-foreground", className),
1991
+ className: cn(
1992
+ "caption-bottom mt-[var(--space-4,1rem)] text-sm text-muted-foreground",
1993
+ className
1994
+ ),
1966
1995
  ...props
1967
1996
  }
1968
1997
  ));
@@ -1975,13 +2004,19 @@ import {
1975
2004
  useReactTable
1976
2005
  } from "@tanstack/react-table";
1977
2006
  import { jsx as jsx35, jsxs as jsxs13 } from "react/jsx-runtime";
1978
- function DataTable({ columns, data }) {
2007
+ function DataTable({
2008
+ columns,
2009
+ data,
2010
+ caption,
2011
+ "aria-label": ariaLabel
2012
+ }) {
1979
2013
  const table = useReactTable({
1980
2014
  data,
1981
2015
  columns,
1982
2016
  getCoreRowModel: getCoreRowModel()
1983
2017
  });
1984
- return /* @__PURE__ */ jsx35("div", { className: "rounded-md border", children: /* @__PURE__ */ jsxs13(Table, { children: [
2018
+ return /* @__PURE__ */ jsx35("div", { className: "rounded-md border", children: /* @__PURE__ */ jsxs13(Table, { "aria-label": ariaLabel, children: [
2019
+ caption ? /* @__PURE__ */ jsx35(TableCaption, { children: caption }) : null,
1985
2020
  /* @__PURE__ */ jsx35(TableHeader, { children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ jsx35(TableRow, { children: headerGroup.headers.map((header) => /* @__PURE__ */ jsx35(TableHead, { children: header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext()) }, header.id)) }, headerGroup.id)) }),
1986
2021
  /* @__PURE__ */ jsx35(TableBody, { children: table.getRowModel().rows?.length ? table.getRowModel().rows.map((row) => /* @__PURE__ */ jsx35(TableRow, { "data-state": row.getIsSelected() && "selected", children: row.getVisibleCells().map((cell) => /* @__PURE__ */ jsx35(TableCell, { children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id)) }, row.id)) : /* @__PURE__ */ jsx35(TableRow, { children: /* @__PURE__ */ jsx35(TableCell, { colSpan: columns.length, className: "h-24 text-center", children: "No results." }) }) })
1987
2022
  ] }) });
@@ -2350,7 +2385,7 @@ function CommandDialog({
2350
2385
  /* @__PURE__ */ jsx40(DialogTitle, { children: title }),
2351
2386
  /* @__PURE__ */ jsx40(DialogDescription, { children: description })
2352
2387
  ] }),
2353
- /* @__PURE__ */ jsx40(DialogContent, { className: "overflow-hidden p-0", children: /* @__PURE__ */ jsx40(Command, { className: "[&_[cmdk-group-heading]]:px-[var(--space-2,0.5rem)] [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-[var(--space-2,0.5rem)] [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-[var(--space-2,0.5rem)] [&_[cmdk-item]]:py-[var(--space-3,0.75rem)] [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5", children }) })
2388
+ /* @__PURE__ */ jsx40(DialogContent, { className: "overflow-hidden p-0", scrollable: false, children: /* @__PURE__ */ jsx40(Command, { className: "[&_[cmdk-group-heading]]:px-[var(--space-2,0.5rem)] [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-[var(--space-2,0.5rem)] [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-[var(--space-2,0.5rem)] [&_[cmdk-item]]:py-[var(--space-3,0.75rem)] [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5", children }) })
2354
2389
  ] });
2355
2390
  }
2356
2391
  var CommandInput = React35.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxs17("div", { className: "flex items-center border-b px-[var(--space-3,0.75rem)]", "cmdk-input-wrapper": "", children: [
@@ -2409,9 +2444,11 @@ var CommandGroup = React35.forwardRef(({ className, ...props }, ref) => /* @__PU
2409
2444
  ));
2410
2445
  CommandGroup.displayName = "CommandGroup";
2411
2446
  var CommandSeparator = React35.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx40(
2412
- CommandPrimitive.Separator,
2447
+ "div",
2413
2448
  {
2414
2449
  ref,
2450
+ role: "none",
2451
+ "data-cmdk-separator": "",
2415
2452
  className: cn("-mx-[var(--space-1,0.25rem)] h-px bg-border", className),
2416
2453
  ...props
2417
2454
  }
@@ -2458,9 +2495,11 @@ function Combobox({
2458
2495
  emptyText = "No results found.",
2459
2496
  disabled,
2460
2497
  className,
2461
- "aria-label": ariaLabel
2498
+ "aria-label": ariaLabel,
2499
+ "aria-labelledby": ariaLabelledBy
2462
2500
  }) {
2463
2501
  const [open, setOpen] = React36.useState(false);
2502
+ const listboxId = React36.useId();
2464
2503
  const selected = options.find((o) => o.value === value);
2465
2504
  return /* @__PURE__ */ jsxs18(Popover, { open, onOpenChange: setOpen, children: [
2466
2505
  /* @__PURE__ */ jsx41(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs18(
@@ -2470,7 +2509,9 @@ function Combobox({
2470
2509
  role: "combobox",
2471
2510
  "aria-expanded": open,
2472
2511
  "aria-haspopup": "listbox",
2512
+ "aria-controls": open ? listboxId : void 0,
2473
2513
  "aria-label": ariaLabel,
2514
+ "aria-labelledby": ariaLabelledBy,
2474
2515
  disabled,
2475
2516
  className: cn(
2476
2517
  "inline-flex h-[var(--control-height-md,2.5rem)] w-[240px] items-center justify-between gap-[var(--gap-sm,0.5rem)] rounded-md border border-input bg-background px-[var(--space-3,0.75rem)] py-[var(--space-2,0.5rem)] text-sm font-normal transition-all duration-[var(--duration-normal,200ms)] ease-out",
@@ -2502,7 +2543,7 @@ function Combobox({
2502
2543
  ) }),
2503
2544
  /* @__PURE__ */ jsx41(PopoverContent, { className: "w-[240px] p-0", align: "start", children: /* @__PURE__ */ jsxs18(Command, { children: [
2504
2545
  /* @__PURE__ */ jsx41(CommandInput, { placeholder: searchPlaceholder }),
2505
- /* @__PURE__ */ jsxs18(CommandList, { children: [
2546
+ /* @__PURE__ */ jsxs18(CommandList, { id: listboxId, children: [
2506
2547
  /* @__PURE__ */ jsx41(CommandEmpty, { children: emptyText }),
2507
2548
  /* @__PURE__ */ jsx41(CommandGroup, { children: options.map((option) => /* @__PURE__ */ jsxs18(
2508
2549
  CommandItem,
@@ -3706,7 +3747,7 @@ var dialogSchema = {
3706
3747
  "Putting too many primary actions in DialogFooter"
3707
3748
  ],
3708
3749
  relatedComponents: ["alert-dialog", "popover", "sheet"],
3709
- accessibilityNotes: "Radix traps focus, handles Escape to close, and wires aria-labelledby/describedby to DialogTitle/DialogDescription. Always include a DialogTitle.",
3750
+ accessibilityNotes: "Radix traps focus, handles Escape to close, and wires aria-labelledby/describedby to DialogTitle/DialogDescription. Always include a DialogTitle. DialogContent is constrained to `max-h-[calc(100vh-2rem)]` and scrolls internally so long content stays inside the focus trap.",
3710
3751
  tokenBudget: 600
3711
3752
  },
3712
3753
  tags: ["dialog", "modal", "overlay", "popup", "form"]
@@ -4224,6 +4265,24 @@ var sliderSchema = {
4224
4265
  default: "horizontal",
4225
4266
  description: "Slider direction",
4226
4267
  enumValues: ["horizontal", "vertical"]
4268
+ },
4269
+ {
4270
+ name: "aria-label",
4271
+ type: "string",
4272
+ required: false,
4273
+ description: "Accessible label for the slider as a whole. Mirrored onto a single thumb automatically; for range sliders prefer thumbLabels."
4274
+ },
4275
+ {
4276
+ name: "aria-labelledby",
4277
+ type: "string",
4278
+ required: false,
4279
+ description: "Id of an external visible label that names the slider."
4280
+ },
4281
+ {
4282
+ name: "thumbLabels",
4283
+ type: "object",
4284
+ required: false,
4285
+ description: "Per-thumb accessible labels (string[]). Required for range sliders so each thumb has a meaningful name (e.g. ['Minimum price', 'Maximum price']). For a single-thumb slider, the Root's aria-label is mirrored onto the thumb automatically and thumbLabels is only needed when overriding that default."
4227
4286
  }
4228
4287
  ],
4229
4288
  variants: [],
@@ -4253,10 +4312,12 @@ var sliderSchema = {
4253
4312
  "Using Slider for exact values without showing the number",
4254
4313
  "Missing min/max bounds",
4255
4314
  "Using step=1 for fractional values (set step=0.01)",
4256
- "Not providing aria-label when there's no visible label"
4315
+ "Not providing aria-label / aria-labelledby when there's no visible label",
4316
+ "Range slider with only Root aria-label and no thumbLabels \u2014 both thumbs fall back to '(N of 2)' indexed names instead of meaningful per-thumb labels",
4317
+ "thumbLabels.length !== value.length \u2014 extra labels are ignored, missing labels fall back to indexed names (dev-mode warning)"
4257
4318
  ],
4258
4319
  relatedComponents: ["input"],
4259
- accessibilityNotes: "Arrow keys step by step, Home/End jump to min/max, PageUp/PageDown step larger. Radix handles aria-valuemin/max/now. Add aria-label if no visible label.",
4320
+ accessibilityNotes: "Arrow keys step by step, Home/End jump to min/max, PageUp/PageDown step larger. Radix handles aria-valuemin/max/now. Each thumb has its own accessible name: explicit via thumbLabels[i], else mirrored from the Root's aria-label (single thumb) or indexed '(i of N)' fallback (range). Add aria-label / aria-labelledby on the Root when there's no visible label.",
4260
4321
  tokenBudget: 450
4261
4322
  },
4262
4323
  tags: ["slider", "range", "form", "numeric", "input"]
@@ -4683,7 +4744,14 @@ var scrollAreaSchema = {
4683
4744
  description: "When scrollbars are visible",
4684
4745
  enumValues: ["auto", "always", "scroll", "hover"]
4685
4746
  },
4686
- { name: "className", type: "string", required: false, description: "Set dimensions via Tailwind (e.g. h-72 w-48)" }
4747
+ { name: "className", type: "string", required: false, description: "Set dimensions via Tailwind (e.g. h-72 w-48)" },
4748
+ {
4749
+ name: "viewportTabIndex",
4750
+ type: "number",
4751
+ required: false,
4752
+ default: 0,
4753
+ description: "tabIndex applied to the scroll viewport. Defaults to 0 so keyboard users can scroll without a pointer; pass -1 to skip the viewport in the tab order when wrapping decorative or already-keyboard-reachable content."
4754
+ }
4687
4755
  ],
4688
4756
  variants: [],
4689
4757
  slots: [
@@ -4713,10 +4781,11 @@ var scrollAreaSchema = {
4713
4781
  commonMistakes: [
4714
4782
  "Forgetting to set height/width \u2014 scrollbars don't appear",
4715
4783
  "Using for the whole page",
4716
- "Nesting ScrollAreas (confusing UX)"
4784
+ "Nesting ScrollAreas (confusing UX)",
4785
+ "Wrapping decorative or already-keyboard-reachable content without setting viewportTabIndex={-1} \u2014 adds an unnecessary tab stop"
4717
4786
  ],
4718
4787
  relatedComponents: [],
4719
- accessibilityNotes: "Radix doesn't make the viewport focusable by default \u2014 add tabIndex={0} on a focusable child or the viewport to enable keyboard scrolling (arrow keys, PgUp/PgDn, Home/End). For long lists, consider pagination or virtualization.",
4788
+ accessibilityNotes: "The viewport is keyboard-focusable by default (viewportTabIndex=0) so users can scroll long content via arrow keys / PgUp / PgDn / Home / End without a pointer. Pass viewportTabIndex={-1} when the contents are already in the tab order or purely decorative. For very long lists, consider pagination or virtualization.",
4720
4789
  tokenBudget: 350
4721
4790
  },
4722
4791
  tags: ["scroll-area", "scroll", "overflow", "scrollbar", "layout"]
@@ -5250,7 +5319,19 @@ var dataTableSchema = {
5250
5319
  subcategory: "data",
5251
5320
  props: [
5252
5321
  { name: "columns", type: "object", required: true, description: "ColumnDef<TData, TValue>[] from @tanstack/react-table" },
5253
- { name: "data", type: "object", required: true, description: "Array of row data" }
5322
+ { name: "data", type: "object", required: true, description: "Array of row data" },
5323
+ {
5324
+ name: "caption",
5325
+ type: "ReactNode",
5326
+ required: false,
5327
+ description: "Visible caption rendered below the table; announced by screen readers when entering the table"
5328
+ },
5329
+ {
5330
+ name: "aria-label",
5331
+ type: "string",
5332
+ required: false,
5333
+ description: "Accessible label forwarded as aria-label on the underlying <table>; use when no visible caption is shown"
5334
+ }
5254
5335
  ],
5255
5336
  variants: [],
5256
5337
  slots: [],
@@ -5274,10 +5355,11 @@ var dataTableSchema = {
5274
5355
  "Forgetting getCoreRowModel() (table renders nothing)",
5275
5356
  "Recreating columns array on every render (breaks memoization \u2014 wrap in useMemo or define outside the component)",
5276
5357
  "Using accessorKey with nested paths without accessorFn",
5277
- "Not adding filter/sort row models when those features are needed"
5358
+ "Not adding filter/sort row models when those features are needed",
5359
+ "Shipping a table without `caption` or `aria-label` \u2014 the table is unlabelled to assistive tech"
5278
5360
  ],
5279
5361
  relatedComponents: ["table", "pagination"],
5280
- accessibilityNotes: "Uses semantic <table> via Hex UI Table primitives. Add aria-sort to sortable column headers. Announce filter/sort changes via aria-live for dynamic updates.",
5362
+ accessibilityNotes: "Pass either `caption` (visible) or `aria-label` so screen readers announce the table when the user enters it. Add aria-sort to sortable column headers. Announce filter/sort changes via aria-live for dynamic updates.",
5281
5363
  tokenBudget: 900
5282
5364
  },
5283
5365
  tags: ["data-table", "tanstack", "sortable", "filterable", "paginated"]
@@ -5691,10 +5773,11 @@ var commandSchema = {
5691
5773
  "Forgetting CommandList \u2014 items won't be scrollable or grouped properly",
5692
5774
  "Giving CommandItem non-unique values (breaks filtering and controlled state)",
5693
5775
  "Overriding CommandInput className to remove the border/padding \u2014 breaks the \u2318K icon layout",
5694
- "Not rendering CommandEmpty \u2014 the list looks broken when a search has no matches"
5776
+ "Not rendering CommandEmpty \u2014 the list looks broken when a search has no matches",
5777
+ "Querying CommandSeparator via cmdk's internal Separator state \u2014 Hex UI renders it as a presentational div with role='none' (and the `data-cmdk-separator` attribute preserved for selector compatibility) so it can sit inside CommandList's role=listbox without violating ARIA"
5695
5778
  ],
5696
5779
  relatedComponents: ["combobox", "dialog", "dropdown-menu"],
5697
- accessibilityNotes: "cmdk wires role=listbox/option and aria-activedescendant. Use the `label` prop on Command for a screen-reader-only name when no visible heading exists.",
5780
+ accessibilityNotes: "cmdk wires role=listbox/option and aria-activedescendant. Use the `label` prop on Command for a screen-reader-only name when no visible heading exists. CommandSeparator renders with role='none' (still selectable via `[data-cmdk-separator]`) so listbox-children rules are satisfied.",
5698
5781
  tokenBudget: 900
5699
5782
  },
5700
5783
  tags: ["command", "cmdk", "palette", "search", "launcher"]
@@ -5758,7 +5841,13 @@ var comboboxSchema = {
5758
5841
  name: "aria-label",
5759
5842
  type: "string",
5760
5843
  required: false,
5761
- description: "Accessible label \u2014 required when no adjacent visible <label> is used"
5844
+ description: "Accessible label \u2014 required when no adjacent visible label is used"
5845
+ },
5846
+ {
5847
+ name: "aria-labelledby",
5848
+ type: "string",
5849
+ required: false,
5850
+ description: "Id of an external visible label that names the combobox"
5762
5851
  }
5763
5852
  ],
5764
5853
  variants: [],
@@ -5789,10 +5878,10 @@ var comboboxSchema = {
5789
5878
  "Using the label as the value \u2014 fine if stable, but prefer a short stable `value` string",
5790
5879
  "Forgetting to bind value + onChange \u2014 uncontrolled mode doesn't exist on this wrapper",
5791
5880
  "Mixing translated labels without keying on value \u2014 label changes won't update selection",
5792
- "Missing aria-label when there's no adjacent visible <label>"
5881
+ "Missing aria-label / aria-labelledby \u2014 role='combobox' does not allow name from contents, so without one of these the trigger has no accessible name"
5793
5882
  ],
5794
5883
  relatedComponents: ["command", "popover", "select"],
5795
- accessibilityNotes: "Trigger has role='combobox' + aria-expanded. Popover traps focus inside the CommandInput. Options are announced via cmdk's aria-activedescendant wiring.",
5884
+ accessibilityNotes: "Trigger has role='combobox' + aria-expanded + aria-haspopup='listbox'. aria-controls points at the inner CommandList (a useId-stabilized listbox). Pass aria-label or aria-labelledby \u2014 combobox does not derive its name from contents.",
5796
5885
  tokenBudget: 900
5797
5886
  },
5798
5887
  tags: ["combobox", "select", "search", "cmdk", "input"]