Slider
A control element that allows for a range of selections.
Usage
import { Slider, type SliderProps } from '~/components/ui'
export const Demo = (props: SliderProps) => {
return (
<Slider
value={[33]}
marks={[
{ value: 25, label: '25' },
{ value: 50, label: '50' },
{ value: 75, label: '75' },
]}
{...props}
/>
)
}
Examples
Range Slider
To use the slider as a range slider, you need to provide an array of values.
<Slider value={[33, 66]}>Label</Slider>
Unlabeled
The slider can also be used without a label while still showing indicators within the track.
<Slider defaultValue={[33]} marks={[{ value: 0 }, { value: 25 }, { value: 50 }, { value: 75 }, { value: 100 }]} />
Installation
npx @park-ui/cli components add slider
1
Styled Primitive
Copy the code snippet below into ~/components/ui/primitives/slider.tsx
'use client'
import type { Assign } from '@ark-ui/react'
import { Slider } from '@ark-ui/react/slider'
import { type SliderVariantProps, slider } from 'styled-system/recipes'
import type { ComponentProps, HTMLStyledProps } from 'styled-system/types'
import { createStyleContext } from '~/lib/create-style-context'
const { withProvider, withContext } = createStyleContext(slider)
export type RootProviderProps = ComponentProps<typeof RootProvider>
export const RootProvider = withProvider<
HTMLDivElement,
Assign<Assign<HTMLStyledProps<'div'>, Slider.RootProviderBaseProps>, SliderVariantProps>
>(Slider.RootProvider, 'root')
export type RootProps = ComponentProps<typeof Root>
export const Root = withProvider<
HTMLDivElement,
Assign<Assign<HTMLStyledProps<'div'>, Slider.RootBaseProps>, SliderVariantProps>
>(Slider.Root, 'root')
export const Control = withContext<
HTMLDivElement,
Assign<HTMLStyledProps<'div'>, Slider.ControlBaseProps>
>(Slider.Control, 'control')
export const Label = withContext<
HTMLLabelElement,
Assign<HTMLStyledProps<'label'>, Slider.LabelBaseProps>
>(Slider.Label, 'label')
export const MarkerGroup = withContext<
HTMLDivElement,
Assign<HTMLStyledProps<'div'>, Slider.MarkerGroupBaseProps>
>(Slider.MarkerGroup, 'markerGroup')
export const Marker = withContext<
HTMLSpanElement,
Assign<HTMLStyledProps<'span'>, Slider.MarkerBaseProps>
>(Slider.Marker, 'marker')
export const Range = withContext<
HTMLDivElement,
Assign<HTMLStyledProps<'div'>, Slider.RangeBaseProps>
>(Slider.Range, 'range')
export const Thumb = withContext<
HTMLDivElement,
Assign<HTMLStyledProps<'div'>, Slider.ThumbBaseProps>
>(Slider.Thumb, 'thumb')
export const Track = withContext<
HTMLDivElement,
Assign<HTMLStyledProps<'div'>, Slider.TrackBaseProps>
>(Slider.Track, 'track')
export const ValueText = withContext<
HTMLDivElement,
Assign<HTMLStyledProps<'span'>, Slider.ValueTextBaseProps>
>(Slider.ValueText, 'valueText')
export {
SliderContext as Context,
SliderHiddenInput as HiddenInput,
} from '@ark-ui/react/slider'
import { type Assign, Slider } from '@ark-ui/solid'
import type { ComponentProps } from 'solid-js'
import { type SliderVariantProps, slider } from 'styled-system/recipes'
import type { HTMLStyledProps } from 'styled-system/types'
import { createStyleContext } from '~/lib/create-style-context'
const { withProvider, withContext } = createStyleContext(slider)
export type RootProviderProps = ComponentProps<typeof RootProvider>
export const RootProvider = withProvider<
Assign<Assign<HTMLStyledProps<'div'>, Slider.RootProviderBaseProps>, SliderVariantProps>
>(Slider.RootProvider, 'root')
export type RootProps = ComponentProps<typeof Root>
export const Root = withProvider<
Assign<Assign<HTMLStyledProps<'div'>, Slider.RootBaseProps>, SliderVariantProps>
>(Slider.Root, 'root')
export const Control = withContext<Assign<HTMLStyledProps<'div'>, Slider.ControlBaseProps>>(
Slider.Control,
'control',
)
export const Label = withContext<Assign<HTMLStyledProps<'label'>, Slider.LabelBaseProps>>(
Slider.Label,
'label',
)
export const MarkerGroup = withContext<Assign<HTMLStyledProps<'div'>, Slider.MarkerGroupBaseProps>>(
Slider.MarkerGroup,
'markerGroup',
)
export const Marker = withContext<Assign<HTMLStyledProps<'span'>, Slider.MarkerBaseProps>>(
Slider.Marker,
'marker',
)
export const Range = withContext<Assign<HTMLStyledProps<'div'>, Slider.RangeBaseProps>>(
Slider.Range,
'range',
)
export const Thumb = withContext<Assign<HTMLStyledProps<'div'>, Slider.ThumbBaseProps>>(
Slider.Thumb,
'thumb',
)
export const Track = withContext<Assign<HTMLStyledProps<'div'>, Slider.TrackBaseProps>>(
Slider.Track,
'track',
)
export const ValueText = withContext<Assign<HTMLStyledProps<'span'>, Slider.ValueTextBaseProps>>(
Slider.ValueText,
'valueText',
)
export {
SliderContext as Context,
SliderHiddenInput as HiddenInput,
} from '@ark-ui/solid'
No snippet found
Extend ~/components/ui/primitives/index.ts
with the following line:
export * as Slider from './slider'
2
Add Composition
Copy the code snippet below into ~/components/ui/slider.tsx
'use client'
import { type ReactNode, forwardRef } from 'react'
import { Slider as ArkSlider } from '~/components/ui/primitives'
export interface SliderProps extends ArkSlider.RootProps {
children?: ReactNode
marks?: {
value: number
label?: ReactNode
}[]
}
export const Slider = forwardRef<HTMLDivElement, SliderProps>((props, ref) => {
const { children, marks, ...rootProps } = props
return (
<ArkSlider.Root ref={ref} {...rootProps}>
<ArkSlider.Context>
{(api) => (
<>
{children && <ArkSlider.Label>{children}</ArkSlider.Label>}
<ArkSlider.Control>
<ArkSlider.Track>
<ArkSlider.Range />
</ArkSlider.Track>
{api.value.map((_, index) => (
<ArkSlider.Thumb key={index} index={index}>
<ArkSlider.HiddenInput />
</ArkSlider.Thumb>
))}
</ArkSlider.Control>
{props.marks && (
<ArkSlider.MarkerGroup>
{props.marks.map((mark) => (
<ArkSlider.Marker key={mark.value} value={mark.value}>
{mark.label}
</ArkSlider.Marker>
))}
</ArkSlider.MarkerGroup>
)}
</>
)}
</ArkSlider.Context>
</ArkSlider.Root>
)
})
Slider.displayName = 'Slider'
import { Index, type JSX, Show, children, splitProps } from 'solid-js'
import { Slider as ArkSlider } from '~/components/ui/primitives'
export interface SliderProps extends ArkSlider.RootProps {
marks?: {
value: number
label?: JSX.Element
}[]
}
export const Slider = (props: SliderProps) => {
const [localProps, rootProps] = splitProps(props, ['children', 'marks'])
const getChildren = children(() => localProps.children)
return (
<ArkSlider.Root {...rootProps}>
<ArkSlider.Context>
{(slider) => (
<>
<Show when={getChildren()}>
<ArkSlider.Label>{getChildren()}</ArkSlider.Label>
</Show>
<ArkSlider.Control>
<ArkSlider.Track>
<ArkSlider.Range />
</ArkSlider.Track>
<Index each={slider().value}>
{(_, index) => (
<ArkSlider.Thumb index={index}>
<ArkSlider.HiddenInput />
</ArkSlider.Thumb>
)}
</Index>
</ArkSlider.Control>
<Show when={localProps.marks}>
<ArkSlider.MarkerGroup>
<Index each={localProps.marks}>
{(mark) => (
<ArkSlider.Marker value={mark().value}>{mark().label}</ArkSlider.Marker>
)}
</Index>
</ArkSlider.MarkerGroup>
</Show>
</>
)}
</ArkSlider.Context>
</ArkSlider.Root>
)
}
Extend ~/components/ui/index.ts
with the following line:
export * from './primitives'
export { Slider, type SliderProps } from './slider'
3
Integrate Recipe
If you're not using @park-ui/preset
, add the following recipe to yourpanda.config.ts
:
import { sliderAnatomy } from '@ark-ui/anatomy'
import { defineSlotRecipe } from '@pandacss/dev'
export const slider = defineSlotRecipe({
className: 'slider',
slots: sliderAnatomy.keys(),
base: {
root: {
colorPalette: 'accent',
display: 'flex',
flexDirection: 'column',
gap: '1',
width: 'full',
},
control: {
position: 'relative',
display: 'flex',
alignItems: 'center',
},
track: {
backgroundColor: 'bg.emphasized',
borderRadius: 'full',
overflow: 'hidden',
flex: '1',
},
range: {
background: 'colorPalette.default',
},
thumb: {
background: 'bg.default',
borderColor: 'colorPalette.default',
borderRadius: 'full',
borderWidth: '2px',
boxShadow: 'sm',
outline: 'none',
zIndex: '1',
},
label: {
color: 'fg.default',
fontWeight: 'medium',
},
markerGroup: {
mt: '-1',
},
marker: {
'--before-background': {
base: 'white',
_dark: 'colors.colorPalette.fg',
},
color: 'fg.muted',
_before: {
background: 'white',
borderRadius: 'full',
content: "''",
display: 'block',
left: '50%',
position: 'relative',
transform: 'translateX(-50%)',
},
_underValue: {
_before: {
background: 'var(--before-background)',
},
},
},
},
defaultVariants: {
size: 'md',
},
variants: {
size: {
sm: {
control: {
height: '4',
},
range: {
height: '1.5',
},
track: {
height: '1.5',
},
thumb: {
height: '4',
width: '4',
},
marker: {
_before: {
height: '1',
top: '-2.5',
width: '1',
},
textStyle: 'sm',
},
label: {
textStyle: 'sm',
},
},
md: {
control: {
height: '5',
},
range: {
height: '2',
},
track: {
height: '2',
},
thumb: {
height: '5',
width: '5',
},
marker: {
_before: {
height: '1',
top: '-3',
width: '1',
},
textStyle: 'sm',
},
label: {
textStyle: 'sm',
},
},
lg: {
control: {
height: '6',
},
range: {
height: '2.5',
},
track: {
height: '2.5',
},
thumb: {
height: '6',
width: '6',
},
marker: {
_before: {
height: '1.5',
top: '-15px',
width: '1.5',
},
textStyle: 'md',
},
label: {
textStyle: 'md',
},
},
},
},
})