Skip to content

feat(SliderArea): add SliderArea component#2460

Open
GrandMagus02 wants to merge 16 commits intounovue:v2from
GrandMagus02:feat/slider-area
Open

feat(SliderArea): add SliderArea component#2460
GrandMagus02 wants to merge 16 commits intounovue:v2from
GrandMagus02:feat/slider-area

Conversation

@GrandMagus02
Copy link

@GrandMagus02 GrandMagus02 commented Feb 16, 2026

🔗 Linked issue

#2455

❓ Type of change

  • 📖 Documentation (updates to the documentation, readme or JSdoc annotations)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • 👌 Enhancement (improving an existing functionality like performance)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📚 Description

Implemented the new SliderArea component set, enabling a fully accessible 2D slider for selecting (x, y) values via one or multiple thumbs within a rectangular track.

This adds the core primitives (SliderAreaRoot, SliderAreaTrack, SliderAreaRegion, SliderAreaThumb, SliderAreaThumbX, SliderAreaThumbY) with controlled and uncontrolled value support, keyboard interactions, pointer/touch dragging, RTL handling, disabled state, stepping, and optional axis inversion—following the existing Slider patterns and API conventions.

Resolves #2455

📸 Screenshots (if appropriate)

image image

📝 Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

Summary by CodeRabbit

Release Notes

  • New Features
    • Added SliderArea component for 2D slider interactions with support for single or multiple thumbs.
    • Configurable step sizes, value ranges, axis inversion, and thumb alignment options.
    • Full accessibility support including keyboard navigation and ARIA attributes.
    • Complete documentation and interactive examples included.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 16, 2026

📝 Walkthrough

Walkthrough

A new SliderArea component—a 2D slider for selecting (x, y) coordinates—is introduced with six sub-components (Root, Track, Region, Thumb, ThumbX, ThumbY), keyboard and pointer interaction support, comprehensive documentation, test coverage, story variants, and API exports.

Changes

Cohort / File(s) Summary
Core SliderArea Components
packages/core/src/SliderArea/SliderAreaRoot.vue, SliderAreaImpl.vue, SliderAreaTrack.vue, SliderAreaRegion.vue, SliderAreaThumb.vue, SliderAreaThumbX.vue, SliderAreaThumbY.vue
Implementation of 2D slider with multi-thumb support, keyboard/pointer interaction, step snapping, min-gap constraints between thumbs, invertX/invertY, RTL direction support, disabled state, thumb alignment (contain/overflow), and context-based coordination across thumb X/Y axes.
Utilities and Type Exports
packages/core/src/SliderArea/utils.ts, index.ts
Helper functions for step snapping and closest-thumb-index calculation; context management for thumb coordinates; barrel export consolidating all SliderArea components, props interfaces, emits, and utility types.
Public API Integration
packages/core/src/index.ts, packages/core/constant/components.ts
Re-exports SliderArea module from core package root; adds SliderArea component entries to component registry.
Comprehensive Test Suite
packages/core/src/SliderArea/SliderArea.test.ts
Tests for accessibility (ARIA, roving tabindex), keyboard navigation (arrows, Home/End, Page Up/Down, shift-multiplier), pointer interactions (pointerdown/move/up), disabled states, form integration, and prop-driven variations (invertedX, invertedY).
Documentation Pages
docs/content/docs/components/slider-area.md, docs/content/meta/SliderArea{Root,Track,Region,Thumb,ThumbX,ThumbY}.md
Main component guide with usage, anatomy, configuration examples, accessibility notes; auto-generated API reference tables for props, emits, and slots for each sub-component.
Demo Components and Config
docs/components/demo/SliderArea/tailwind/index.vue, css/index.vue, tailwind/tailwind.config.js, css/styles.css
Two demo implementations (Tailwind and CSS) with styling; Tailwind configuration for scoped utilities.
Navigation and Integration
docs/.vitepress/config.ts, docs/components/Demos.vue
Adds SliderArea link to docs navigation menu and demo container to component showcase page.
Story Files
packages/core/src/SliderArea/story/SliderAreaChromatic.story.vue, SliderAreaDemo.story.vue, SliderAreaExtremes.story.vue, _SliderArea.vue
Visual test variants covering uncontrolled/controlled modes, RTL, disabled, custom steps, axis inversion, thumb alignment, min-gap constraints, and extreme edge cases.

Sequence Diagram

sequenceDiagram
    actor User
    participant SliderAreaRoot
    participant SliderAreaImpl
    participant SliderAreaThumbX
    participant SliderAreaThumbY
    participant Context as Thumb Context
    
    User->>SliderAreaRoot: Pointer Down on Track
    SliderAreaRoot->>SliderAreaRoot: getPointFromPointerEvent()
    SliderAreaRoot->>SliderAreaRoot: getClosestThumbIndex()
    SliderAreaRoot->>SliderAreaImpl: slideStart event
    SliderAreaImpl->>SliderAreaThumbX: focus() + set activeDirection='x'
    SliderAreaImpl->>SliderAreaThumbY: focus() + set activeDirection='y'
    
    User->>SliderAreaRoot: Pointer Move
    SliderAreaRoot->>SliderAreaRoot: getPointFromPointerEvent()
    SliderAreaRoot->>SliderAreaRoot: clampAxis() + updateValues()
    SliderAreaImpl->>SliderAreaImpl: slideMove event
    SliderAreaThumbX->>Context: Update X value
    SliderAreaThumbY->>Context: Update Y value
    SliderAreaRoot->>SliderAreaRoot: emit update:modelValue
    
    User->>SliderAreaRoot: Pointer Up
    SliderAreaImpl->>SliderAreaImpl: slideEnd event
    SliderAreaRoot->>SliderAreaRoot: handleSlideEnd()
    SliderAreaRoot->>SliderAreaRoot: emit valueCommit
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐰 A slider's cousin now hops into view,
With X and Y coordinates, shiny and new!
Two-dimensional dragging, so smooth and so fine,
Keyboard and pointer in perfect design!
From thumbs down to regions, the pieces align,
This SliderArea gem makes interaction divine! 🌟

🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding a SliderArea component feature. It directly reflects the primary objective of the pull request.
Linked Issues check ✅ Passed The PR implements all core requirements from issue #2455: SliderAreaRoot, SliderAreaTrack, SliderAreaRegion, SliderAreaThumb with X/Y variants, controlled/uncontrolled models, axis-specific props, accessibility features, keyboard interactions, pointer support, and comprehensive documentation.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the SliderArea component feature. No unrelated modifications to existing files or out-of-scope features were introduced.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into v2

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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.

Copy link
Contributor

@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: 4

🤖 Fix all issues with AI agents
In `@docs/components/demo/SliderArea/tailwind/index.vue`:
- Around line 20-26: The aria-label on the SliderAreaThumb component is
incorrectly set to "Volume" for a generic 2D slider demo; update the aria-label
value on the SliderAreaThumb element (the attribute in the SliderAreaThumb
component instance) to a neutral, descriptive label such as "2D Slider" or
"Position" to accurately reflect its purpose in the demo and improve
accessibility.

In `@docs/content/docs/components/slider-area.md`:
- Around line 348-354: Add a short clarifying note in the Keyboard Interactions
section of the SliderArea docs explaining that SliderArea uses screen-coordinate
convention by default (Y increases downward) for its 2D use case, which is why
ArrowDown increases Y and ArrowUp decreases Y, and mention that the invertedY
prop on SliderArea can be set to true to invert the vertical axis to match the
WAI-ARIA vertical slider semantics if users need that behavior; update the text
near the ArrowDown/ArrowUp rows and/or add a single-sentence callout referencing
SliderArea and invertedY to keep the Accessibility/WAI-ARIA statement accurate.

In `@docs/content/meta/SliderAreaRoot.md`:
- Around line 3-10: Change the default for the "as" prop in the SliderAreaRoot
component from 'span' to a block-level element 'div' to avoid invalid HTML
nesting; update the prop default in the source component SliderAreaRoot
(packages/core/src/SliderArea/SliderAreaRoot.vue) so the props declaration for
"as" sets default: 'div' (the prop name is "as" in SliderAreaRoot) to match
other container components like Calendar/NumberField/Editable.

In `@packages/core/src/SliderArea/SliderAreaThumb.vue`:
- Around line 55-75: The ARIA attributes aria-roledescription and aria-label are
currently placed on SliderAreaThumb's wrapper Primitive (which has no role),
causing incorrect screen reader announcements; remove aria-label and
aria-roledescription from the Primitive in SliderAreaThumb and instead add
aria-label and aria-roledescription (or at least aria-label) to each focusable
child component SliderAreaThumbX and SliderAreaThumbY (the elements that set
role="slider"), ensuring aria-orientation remains on those child sliders; update
the binding that currently uses ($attrs['aria-label'] as string) || label so the
children receive the resolved label (or fallback) and do not rely on the parent
Primitive for ARIA attributes.
🧹 Nitpick comments (11)
packages/core/src/SliderArea/SliderArea.test.ts (3)

9-18: Duplicated global mocks could be extracted to a shared setup.

The ResizeObserver and pointer capture method mocks are duplicated verbatim between the two top-level describe blocks (lines 10–18 and lines 377–385). Consider extracting them to a file-level beforeAll or a shared helper to reduce duplication.

♻️ Proposed refactor: hoist mocks to file level
+// File-level setup — shared across all describe blocks
+globalThis.ResizeObserver = class ResizeObserver {
+  observe() {}
+  unobserve() {}
+  disconnect() {}
+}
+window.HTMLElement.prototype.scrollIntoView = vi.fn()
+window.HTMLElement.prototype.hasPointerCapture = vi.fn().mockImplementation(id => id)
+window.HTMLElement.prototype.releasePointerCapture = vi.fn()
+window.HTMLElement.prototype.setPointerCapture = vi.fn()
+
 describe('given default SliderArea', () => {
-  globalThis.ResizeObserver = class ResizeObserver {
-    observe() {}
-    unobserve() {}
-    disconnect() {}
-  }
-  window.HTMLElement.prototype.scrollIntoView = vi.fn()
-  window.HTMLElement.prototype.hasPointerCapture = vi.fn().mockImplementation(id => id)
-  window.HTMLElement.prototype.releasePointerCapture = vi.fn()
-  window.HTMLElement.prototype.setPointerCapture = vi.fn()
   ...
 })
 
 describe('given slider area in a form', () => {
-  globalThis.ResizeObserver = class ResizeObserver {
-    observe() {}
-    unobserve() {}
-    disconnect() {}
-  }
-  window.HTMLElement.prototype.scrollIntoView = vi.fn()
-  window.HTMLElement.prototype.hasPointerCapture = vi.fn().mockImplementation(id => id)
-  window.HTMLElement.prototype.releasePointerCapture = vi.fn()
-  window.HTMLElement.prototype.setPointerCapture = vi.fn()
   ...
 })

Also applies to: 376-385


303-325: Consider asserting the emitted valueCommit payload, not just its presence.

The pointer interaction test verifies that valueCommit is emitted with one argument (line 321), but doesn't assert what value was committed. Adding a payload assertion would strengthen this test and guard against regressions in the committed value.


26-33: Note: axe test disables the label rule.

The accessibility test disables the label rule. This is acceptable for a story/demo component, but please ensure that the documentation or real-world usage examples demonstrate proper labeling for the SliderArea (e.g., via aria-label or associated <label> elements) so consumers get it right.

packages/core/src/SliderArea/SliderAreaRegion.vue (1)

53-67: Verify that the parent track element has position: relative.

The region uses position: absolute in its inline style, which requires a positioned ancestor. The demo CSS sets position: relative on .SliderAreaTrack, but consumers using custom styles or the as-child pattern on the track could lose this. Consider documenting this requirement or adding a note in the SliderAreaRegion docs.

docs/components/demo/SliderArea/css/index.vue (1)

17-23: Consider a more descriptive aria-label for the demo.

"Volume" suggests a 1D control. Since this demonstrates a 2D slider area, a label like "Color picker" or "Position" would better illustrate the component's purpose to users reading the docs.

docs/content/docs/components/slider-area.md (1)

403-403: Minor wording nit.

Per static analysis: "abstracts all of the" → "abstracts all the" for conciseness.

Proposed fix
-This example abstracts all of the `SliderArea` parts so it can be used as a self-closing element.
+This example abstracts all the `SliderArea` parts so it can be used as a self-closing element.
packages/core/src/SliderArea/story/_SliderArea.vue (1)

18-34: v-model overrides modelValue from forwarded props.

v-bind="forwarded" spreads all props (including modelValue if passed by parent), but v-model="sliderValue" immediately overrides it with local state. This means any externally passed modelValue is silently ignored. Since this is a story-internal component, it's likely intentional — just flagging for awareness.

packages/core/src/SliderArea/SliderAreaImpl.vue (1)

51-54: PAGE_KEYS in the concat is redundant.

PageUp and PageDown are already handled by the earlier else if branches (lines 43–50), so they can never reach this branch. The PAGE_KEYS.concat(ARROW_KEYS) effectively matches only arrow keys here.

Suggested simplification
-      else if (PAGE_KEYS.concat(ARROW_KEYS).includes(event.key)) {
+      else if (ARROW_KEYS.includes(event.key)) {
packages/core/src/SliderArea/SliderAreaRoot.vue (3)

132-136: isSlidingFromLeft logic is correct but hard to parse.

The double-negative style (!== 'rtl', !== 'ltr') makes the truth table non-obvious at a glance. Consider simplifying to an XOR-style expression.

Optional: clearer expression
 const isSlidingFromLeft = computed(() => {
-  return (dir.value !== 'rtl' && !props.invertedX) || (dir.value !== 'ltr' && props.invertedX)
+  return (dir.value === 'ltr') !== props.invertedX
 })

This reads as: "slide from left when LTR and not inverted, or RTL and inverted" — i.e., inversion flips the direction.


224-245: Deep-comparison via JSON.stringify is adequate here but worth noting.

JSON.stringify for change detection (line 236) works for small arrays of number pairs. For this use case it's fine, but if the component ever supports large numbers of thumbs, a manual comparison would be more efficient.


336-348: Verify valuesBeforeSlideStartRef captures a sufficiently deep snapshot.

Line 337: valuesBeforeSlideStartRef = currentModelValue assigns the shallow-copied array from the computed ([...modelValue.value]). The inner [x, y] arrays are shared references with modelValue. This is safe only because updateValues (line 232) replaces inner arrays with new [finalX, finalY] rather than mutating them in place. If that invariant is ever broken, handleSlideEnd's change detection (line 210) would silently fail.

Consider a defensive deep copy to decouple from this implicit invariant:

Optional: defensive deep copy
 `@pointerdown`="() => {
-  if (!disabled) valuesBeforeSlideStartRef = currentModelValue
+  if (!disabled) valuesBeforeSlideStartRef = currentModelValue.map(p => [...p])
 }"

Comment on lines +20 to +26
<SliderAreaThumb
class="[&>*]:outline-none block w-6 h-6 bg-white rounded-full hover:bg-stone-50 shadow-sm focus-within:outline-none focus-within:shadow-[0_0_0_2px] focus-within:shadow-grass9"
aria-label="Volume"
>
<SliderAreaThumbX />
<SliderAreaThumbY />
</SliderAreaThumb>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

aria-label="Volume" is misleading for a generic 2D slider demo.

This is a general-purpose SliderArea demo, not a volume control. A label like "2D Slider" or "Position" would be more appropriate and less confusing for users reading the docs.

Proposed fix
     <SliderAreaThumb
       class="[&>*]:outline-none block w-6 h-6 bg-white rounded-full hover:bg-stone-50 shadow-sm focus-within:outline-none focus-within:shadow-[0_0_0_2px] focus-within:shadow-grass9"
-      aria-label="Volume"
+      aria-label="2D Slider"
     >
📝 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
<SliderAreaThumb
class="[&>*]:outline-none block w-6 h-6 bg-white rounded-full hover:bg-stone-50 shadow-sm focus-within:outline-none focus-within:shadow-[0_0_0_2px] focus-within:shadow-grass9"
aria-label="Volume"
>
<SliderAreaThumbX />
<SliderAreaThumbY />
</SliderAreaThumb>
<SliderAreaThumb
class="[&>*]:outline-none block w-6 h-6 bg-white rounded-full hover:bg-stone-50 shadow-sm focus-within:outline-none focus-within:shadow-[0_0_0_2px] focus-within:shadow-grass9"
aria-label="2D Slider"
>
<SliderAreaThumbX />
<SliderAreaThumbY />
</SliderAreaThumb>
🤖 Prompt for AI Agents
In `@docs/components/demo/SliderArea/tailwind/index.vue` around lines 20 - 26, The
aria-label on the SliderAreaThumb component is incorrectly set to "Volume" for a
generic 2D slider demo; update the aria-label value on the SliderAreaThumb
element (the attribute in the SliderAreaThumb component instance) to a neutral,
descriptive label such as "2D Slider" or "Position" to accurately reflect its
purpose in the demo and improve accessibility.

Comment on lines +348 to +354
keys: ['ArrowDown'],
description: 'Increases the Y value by the <Code>stepY</Code> amount.',
},
{
keys: ['ArrowUp'],
description: 'Decreases the Y value by the <Code>stepY</Code> amount.',
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, find the SliderArea component files
find . -type f -name "*slider*area*" -o -name "*SliderArea*" | head -20

Repository: unovue/reka-ui

Length of output: 1007


🏁 Script executed:

#!/bin/bash
# Check for keyboard handler implementation
rg -n 'ArrowDown|ArrowUp' --type=vue --type=ts -C3 2>/dev/null | head -100

Repository: unovue/reka-ui

Length of output: 40


🏁 Script executed:

#!/bin/bash
# Look for the SliderArea component source more broadly
fd -i sliderarea packages/ || fd -i slider packages/ | grep -i area | head -10

Repository: unovue/reka-ui

Length of output: 719


🏁 Script executed:

#!/bin/bash
# Check the main implementation files for keyboard handling
wc -l packages/core/src/SliderArea/SliderAreaImpl.vue packages/core/src/SliderArea/SliderAreaRoot.vue

Repository: unovue/reka-ui

Length of output: 177


🏁 Script executed:

#!/bin/bash
# Search for keyboard event handling in SliderArea implementation
cat -n packages/core/src/SliderArea/SliderAreaImpl.vue | head -150

Repository: unovue/reka-ui

Length of output: 3076


🏁 Script executed:

#!/bin/bash
# Search for key handling logic
rg -n "key|Arrow|keyboard" packages/core/src/SliderArea/SliderAreaImpl.vue -A 3

Repository: unovue/reka-ui

Length of output: 770


🏁 Script executed:

#!/bin/bash
# Check SliderAreaRoot.vue for stepKeyDown handler and Y-value logic
rg -n "stepKeyDown|ArrowDown|ArrowUp|decreaseY|increaseY" packages/core/src/SliderArea/SliderAreaRoot.vue -B 2 -A 5

Repository: unovue/reka-ui

Length of output: 353


🏁 Script executed:

#!/bin/bash
# Get more context from SliderAreaRoot.vue
sed -n '1,100p' packages/core/src/SliderArea/SliderAreaRoot.vue | cat -n

Repository: unovue/reka-ui

Length of output: 4317


🏁 Script executed:

#!/bin/bash
# Check the full keyboard interactions section and WAI-ARIA reference
sed -n '320,360p' docs/content/docs/components/slider-area.md | cat -n

Repository: unovue/reka-ui

Length of output: 1361


🏁 Script executed:

#!/bin/bash
# Check if invertedY is documented in the keyboard section or props
sed -n '340,400p' docs/content/docs/components/slider-area.md | cat -n

Repository: unovue/reka-ui

Length of output: 2276


🏁 Script executed:

#!/bin/bash
# Search for invertedY prop documentation
rg -n "invertedY" docs/content/docs/components/slider-area.md -B 2 -A 3

Repository: unovue/reka-ui

Length of output: 528


Add a clarifying note about SliderArea's screen-coordinate convention.

The keyboard interactions table accurately describes the component's behavior (ArrowDown increases Y, ArrowUp decreases Y), but the documentation doesn't explain why this deviates from the WAI-ARIA slider pattern referenced in the Accessibility section. While the invertedY prop is documented as a way to invert axes, users may be confused seeing "Adheres to the [Slider WAI-ARIA design pattern]" immediately before interactions that don't match that pattern by default.

Consider adding a brief note in the Keyboard Interactions section clarifying that SliderArea uses screen-coordinate convention by default (where Y increases downward) for the 2D use case, and that invertedY can be used to match WAI-ARIA vertical slider semantics if needed.

🤖 Prompt for AI Agents
In `@docs/content/docs/components/slider-area.md` around lines 348 - 354, Add a
short clarifying note in the Keyboard Interactions section of the SliderArea
docs explaining that SliderArea uses screen-coordinate convention by default (Y
increases downward) for its 2D use case, which is why ArrowDown increases Y and
ArrowUp decreases Y, and mention that the invertedY prop on SliderArea can be
set to true to invert the vertical axis to match the WAI-ARIA vertical slider
semantics if users need that behavior; update the text near the
ArrowDown/ArrowUp rows and/or add a single-sentence callout referencing
SliderArea and invertedY to keep the Accessibility/WAI-ARIA statement accurate.

Comment on lines +3 to +10
<PropsTable :data="[
{
'name': 'as',
'description': '<p>The element or component this component should render as. Can be overwritten by <code>asChild</code>.</p>\n',
'type': 'AsTag | Component',
'required': false,
'default': '\'span\''
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check what `as` defaults to in SliderAreaRoot.vue
echo "=== SliderAreaRoot.vue ==="
find . -name "SliderAreaRoot.vue" -type f | head -5

Repository: unovue/reka-ui

Length of output: 134


🏁 Script executed:

#!/bin/bash
# Find and examine SliderAreaRoot.vue
fd "SliderAreaRoot.vue" -x cat -n {}

# Also check the component structure
echo ""
echo "=== Other Root components for comparison ==="
fd "Root.vue" packages/core/src -x sh -c 'echo "File: {}"; head -50 {}'

Repository: unovue/reka-ui

Length of output: 50370


🏁 Script executed:

#!/bin/bash
# Search for all Root.vue files and their `as:` defaults
echo "=== Searching all Root components for 'as' defaults ==="
fd "Root.vue" packages/core/src -x sh -c '
  file="{}"
  echo "File: $file"
  grep -A 15 "withDefaults(defineProps" "$file" | grep "as:" | head -1
'

Repository: unovue/reka-ui

Length of output: 2540


Root component defaults to span — should be div or block-level element

SliderAreaRoot defaults the as prop to 'span', but this is a container holding block-level children (track, thumbs, input). A span is an inline element; nesting block content inside it violates HTML structure semantics. Other similar container-first components in the library (Calendar, NumberField, Editable) default to 'div'.

Since this file is auto-generated, apply the fix in packages/core/src/SliderArea/SliderAreaRoot.vue line 103.

🤖 Prompt for AI Agents
In `@docs/content/meta/SliderAreaRoot.md` around lines 3 - 10, Change the default
for the "as" prop in the SliderAreaRoot component from 'span' to a block-level
element 'div' to avoid invalid HTML nesting; update the prop default in the
source component SliderAreaRoot
(packages/core/src/SliderArea/SliderAreaRoot.vue) so the props declaration for
"as" sets default: 'div' (the prop name is "as" in SliderAreaRoot) to match
other container components like Calendar/NumberField/Editable.

Comment on lines +55 to +75
<template>
<CollectionItem>
<Primitive
v-bind="$attrs"
:ref="forwardRef"
:aria-label="($attrs['aria-label'] as string) || label"
:data-disabled="rootContext.disabled.value ? '' : undefined"
aria-roledescription="2D slider"
:as-child="asChild"
:as="as"
:style="{
transform: 'var(--reka-slider-area-thumb-transform)',
position: 'absolute',
[rootContext.isSlidingFromLeft.value ? 'left' : 'right']: `calc(${percentX}% + ${thumbInBoundsOffsetX}px)`,
[rootContext.isSlidingFromTop.value ? 'top' : 'bottom']: `calc(${percentY}% + ${thumbInBoundsOffsetY}px)`,
display: !isMounted && value === undefined ? 'none' : undefined,
}"
>
<slot />
</Primitive>
</CollectionItem>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

fd 'SliderAreaThumbX\.vue|SliderAreaThumbY\.vue'

Repository: unovue/reka-ui

Length of output: 157


🏁 Script executed:

cat packages/core/src/SliderArea/SliderAreaThumbX.vue
cat packages/core/src/SliderArea/SliderAreaThumbY.vue

Repository: unovue/reka-ui

Length of output: 3717


Move ARIA attributes to the axis-specific child components.

The aria-roledescription and aria-label are currently on SliderAreaThumb (the parent wrapper), which has no role attribute. However, the actual focusable slider elements are SliderAreaThumbX and SliderAreaThumbY, which have role="slider" but no labels or role descriptions.

ARIA attributes like aria-roledescription should be on elements with semantic roles. This structure causes screen readers to announce the label on an invisible wrapper, then announce two separate sliders without context.

Move aria-label to both child components, or provide a single label on the parent if these are meant as a unified control. Add aria-roledescription to describe the 2D behavior on the child components or consider removing it entirely since each axis is already described as "horizontal"/"vertical" via aria-orientation.

🤖 Prompt for AI Agents
In `@packages/core/src/SliderArea/SliderAreaThumb.vue` around lines 55 - 75, The
ARIA attributes aria-roledescription and aria-label are currently placed on
SliderAreaThumb's wrapper Primitive (which has no role), causing incorrect
screen reader announcements; remove aria-label and aria-roledescription from the
Primitive in SliderAreaThumb and instead add aria-label and aria-roledescription
(or at least aria-label) to each focusable child component SliderAreaThumbX and
SliderAreaThumbY (the elements that set role="slider"), ensuring
aria-orientation remains on those child sliders; update the binding that
currently uses ($attrs['aria-label'] as string) || label so the children receive
the resolved label (or fallback) and do not rely on the parent Primitive for
ARIA attributes.

@zernonia
Copy link
Member

@GrandMagus02 can you check the comment from coderabbit?

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 24, 2026

Open in StackBlitz

npm i https://pkg.pr.new/reka-ui@2460

commit: 3c3ee27

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.

[Feature]: Add SliderArea component

2 participants