
3D Extruded Buildings & Urban Footprints
Real-world 3D building extrusions with dynamic sunlight shadows, terrain DEM elevations, and pitch/bearing camera controls.
accordionuiVertically stacked, collapsible panels — one or many open at a time. Use for FAQs, settings groups, and any place where space is tight but content needs to stay browsable. Built on reka-ui with smooth animation and full keyboard support.
Also available for React ->$pnpm dlx shadcn-vue@latest add https://uipkge.dev/r/vue/accordion.json$npx shadcn-vue@latest add https://uipkge.dev/r/vue/accordion.json$yarn dlx shadcn-vue@latest add https://uipkge.dev/r/vue/accordion.json$bunx shadcn-vue@latest add https://uipkge.dev/r/vue/accordion.jsonnpx shadcn-vue@latest add @uipkge/accordionInstalls to:app/components/ui/accordion/| Name | Type / Values | Default | Required |
|---|---|---|---|
class | HTMLAttributes['class'] | — | optional |
variant | 'default''separated''ghost' | 'default' | optional |
type | 'single''multiple' | — | optional |
modelValue | string | string[] | — | optional |
defaultValue | string | string[] | — | optional |
collapsible | boolean | — | optional |
disabled | boolean | — | optional |
dir | 'ltr''rtl' | — | optional |
orientation | 'horizontal''vertical' | — | optional |
asChild | boolean | — | optional |
as | string | object | — | optional |
<script setup lang="ts">
import { computed, provide } from 'vue'
import type { AccordionRootEmits } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { AccordionRoot, useForwardPropsEmits } from 'reka-ui'
import { cn } from '@/lib/utils'
import { accordionVariants } from './accordion.variants'
// Inlined unions: SFC compiler can't extract runtime props from
// `NonNullable<AccordionVariants['variant']>` or reka-ui's
// `AccordionRootProps`. Inline only the surface we expose.
const props = withDefaults(
defineProps<{
class?: HTMLAttributes['class']
variant?: 'default' | 'separated' | 'ghost'
type?: 'single' | 'multiple'
modelValue?: string | string[]
defaultValue?: string | string[]
collapsible?: boolean
disabled?: boolean
dir?: 'ltr' | 'rtl'
orientation?: 'horizontal' | 'vertical'
asChild?: boolean
as?: string | object
}>(),
{
variant: 'default',
},
)
const emits = defineEmits<AccordionRootEmits>()
const delegated = reactiveOmit(props, 'class', 'variant')
const forwarded = useForwardPropsEmits(delegated, emits)
provide(
Symbol.for('accordionVariant'),
computed(() => props.variant),
)
</script>
<template>
<AccordionRoot
data-uipkge
data-slot="accordion"
:data-variant="variant"
v-bind="forwarded"
:class="cn(accordionVariants({ variant }), props.class)"
>
<slot />
</AccordionRoot>
</template>
<script setup lang="ts">
import type { AccordionContentProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { AccordionContent as RkAccordionContent } from 'reka-ui'
import { cn } from '@/lib/utils'
const props = defineProps<AccordionContentProps & { class?: HTMLAttributes['class'] }>()
const delegated = reactiveOmit(props, 'class')
</script>
<template>
<RkAccordionContent
data-uipkge
data-slot="accordion-content"
v-bind="delegated"
:class="
cn(
'text-muted-foreground overflow-hidden text-sm',
// Height via Reka CSS vars + tw-animate-css (not height:auto).
// duration/ease set --tw-duration/--tw-ease consumed by the utility.
'data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down',
'duration-200 ease-[cubic-bezier(0.32,0.72,0,1)]',
'motion-reduce:animate-none',
props.class,
)
"
>
<div class="pt-0 pb-4">
<slot />
</div>
</RkAccordionContent>
</template>
<script setup lang="ts">
import type { AccordionHeaderProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { AccordionHeader as RkAccordionHeader } from 'reka-ui'
import { cn } from '@/lib/utils'
const props = defineProps<AccordionHeaderProps & { class?: HTMLAttributes['class'] }>()
const delegated = reactiveOmit(props, 'class')
</script>
<template>
<RkAccordionHeader data-uipkge data-slot="accordion-header" v-bind="delegated" :class="cn('flex', props.class)">
<slot />
</RkAccordionHeader>
</template>
<script setup lang="ts">
import { computed, inject } from 'vue'
import type { AccordionItemProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { AccordionItem as RkAccordionItem } from 'reka-ui'
import { cn } from '@/lib/utils'
import { accordionItemVariants } from './accordion.variants'
const props = defineProps<AccordionItemProps & { class?: HTMLAttributes['class'] }>()
const delegated = reactiveOmit(props, 'class')
const variantRef = inject<{ value: 'default' | 'separated' | 'ghost' } | undefined>(
Symbol.for('accordionVariant'),
undefined,
)
const variant = computed(() => variantRef?.value ?? 'default')
</script>
<template>
<RkAccordionItem
data-uipkge
data-slot="accordion-item"
v-bind="delegated"
:class="cn(accordionItemVariants({ variant }), props.class)"
>
<slot />
</RkAccordionItem>
</template>
<script setup lang="ts">
import { computed, inject } from 'vue'
import type { AccordionTriggerProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import { reactiveOmit } from '@vueuse/core'
import { AccordionTrigger as RkAccordionTrigger } from 'reka-ui'
import { ChevronDown } from 'lucide-vue-next'
import { cn } from '@/lib/utils'
import { accordionTriggerVariants } from './accordion.variants'
const props = defineProps<AccordionTriggerProps & { class?: HTMLAttributes['class'] }>()
const delegated = reactiveOmit(props, 'class')
const variantRef = inject<{ value: 'default' | 'separated' | 'ghost' } | undefined>(
Symbol.for('accordionVariant'),
undefined,
)
const variant = computed(() => variantRef?.value ?? 'default')
</script>
<template>
<RkAccordionTrigger
data-uipkge
data-slot="accordion-trigger"
v-bind="delegated"
:class="cn(accordionTriggerVariants({ variant }), props.class)"
>
<slot />
<ChevronDown
class="text-muted-foreground size-4 shrink-0 transition-transform duration-200 ease-[cubic-bezier(0.32,0.72,0,1)] group-data-[state=open]/accordion-trigger:rotate-180 motion-reduce:transition-none"
aria-hidden="true"
/>
</RkAccordionTrigger>
</template>
import type { VariantProps } from 'class-variance-authority'
import { cva } from 'class-variance-authority'
/**
* Variant definitions live in their own file (rather than the package
* `index.ts`) so consuming Vue SFCs can import without creating a circular
* dependency through the index. See card.variants.ts for the canonical
* example + the SSR symptom that motivated the split.
*/
export const accordionVariants = cva('w-full', {
variants: {
variant: {
// Bottom-border between items (the classic shadcn look).
default: '',
// Each item is its own bordered card with a small gap between them.
separated: 'space-y-2',
// Borderless. Use when the parent container already provides framing.
ghost: '',
},
},
defaultVariants: {
variant: 'default',
},
})
export const accordionItemVariants = cva('', {
variants: {
variant: {
default: 'border-b border-border last:border-b-0',
separated: 'rounded-md border border-border bg-card overflow-hidden',
ghost: '',
},
},
defaultVariants: {
variant: 'default',
},
})
export const accordionTriggerVariants = cva(
'group/accordion-trigger flex w-full items-center justify-between gap-3 text-sm font-medium text-foreground outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring/40 hover:text-primary disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
default: 'py-4',
separated: 'px-4 py-3 hover:bg-muted/50',
ghost: 'py-3',
},
},
defaultVariants: {
variant: 'default',
},
},
)
export type AccordionVariants = VariantProps<typeof accordionVariants>
export type AccordionItemVariants = VariantProps<typeof accordionItemVariants>
export type AccordionTriggerVariants = VariantProps<typeof accordionTriggerVariants>
export { default as Accordion } from './Accordion.vue'
export { default as AccordionContent } from './AccordionContent.vue'
export { default as AccordionHeader } from './AccordionHeader.vue'
export { default as AccordionItem } from './AccordionItem.vue'
export { default as AccordionTrigger } from './AccordionTrigger.vue'
// Re-export variant API from the sibling file (kept separate to avoid the
// Component.vue <-> index.ts circular import that broke dev SSR for Card).
export {
accordionVariants,
accordionItemVariants,
accordionTriggerVariants,
type AccordionVariants,
type AccordionItemVariants,
type AccordionTriggerVariants,
} from './accordion.variants'
Raw manifest:https://uipkge.dev/r/vue/accordion.json