
3D Extruded Buildings & Urban Footprints
Real-world 3D building extrusions with dynamic sunlight shadows, terrain DEM elevations, and pitch/bearing camera controls.
listuiList and Item row primitives with List, ListItem, ListItemMedia, ListItemContent, ListItemTitle, ListItemDescription, ListItemActions, and ListSubheader for structured rows, settings lists, and feeds.
Also available for React ->$pnpm dlx shadcn-vue@latest add https://uipkge.dev/r/vue/list.json$npx shadcn-vue@latest add https://uipkge.dev/r/vue/list.json$yarn dlx shadcn-vue@latest add https://uipkge.dev/r/vue/list.json$bunx shadcn-vue@latest add https://uipkge.dev/r/vue/list.jsonnpx shadcn-vue@latest add @uipkge/listInstalls to:app/components/ui/list/| Name | Type / Values | Default | Required |
|---|---|---|---|
class | HTMLAttributes['class'] | — | optional |
as | 'ul''ol''div' | — | optional |
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
as?: 'ul' | 'ol' | 'div'
}>()
</script>
<template>
<component
:is="props.as ?? 'ul'"
data-uipkge
data-slot="list"
:class="cn('list-none space-y-1', props.class)"
v-bind="$attrs"
>
<slot />
</component>
</template>
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { computed, useAttrs } from 'vue'
import { cn } from '@/lib/utils'
defineOptions({ inheritAttrs: false })
const props = defineProps<{
class?: HTMLAttributes['class']
as?: 'li' | 'div' | 'a'
active?: boolean
disabled?: boolean
}>()
const attrs = useAttrs()
// Only show pointer/hover affordances when the item is actually interactive.
const isInteractive = computed(() => {
if (props.disabled) return false
if ((props.as ?? 'li') === 'a') return true
if (attrs.href != null && attrs.href !== false) return true
if (typeof attrs.onClick === 'function') return true
return false
})
</script>
<template>
<component
:is="props.as ?? 'li'"
data-uipkge
data-slot="list-item"
:data-active="active ? '' : undefined"
:data-disabled="disabled ? '' : undefined"
:aria-disabled="disabled ? 'true' : undefined"
:aria-current="active ? 'true' : undefined"
:tabindex="disabled ? -1 : undefined"
:class="
cn(
'rounded-md px-2 py-1.5 text-sm transition-colors duration-200 select-none focus-visible:outline-none',
'has-[>[data-slot=list-item-content]]:flex has-[>[data-slot=list-item-content]]:items-center has-[>[data-slot=list-item-content]]:gap-3',
!disabled && isInteractive && 'hover:bg-accent focus-visible:bg-accent cursor-pointer',
active && 'bg-accent text-accent-foreground',
disabled && 'pointer-events-none cursor-not-allowed opacity-50',
props.class,
)
"
v-bind="$attrs"
>
<slot />
</component>
</template>
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
interface Props {
class?: HTMLAttributes['class']
}
const props = defineProps<Props>()
</script>
<template>
<div
data-uipkge
data-slot="list-item-media"
:class="cn('flex shrink-0 items-center justify-center self-center', props.class)"
>
<slot />
</div>
</template>
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
interface Props {
class?: HTMLAttributes['class']
}
const props = defineProps<Props>()
</script>
<template>
<div
data-uipkge
data-slot="list-item-content"
:class="cn('flex min-w-0 flex-1 flex-col gap-0.5 self-center', props.class)"
>
<slot />
</div>
</template>
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
interface Props {
class?: HTMLAttributes['class']
}
const props = defineProps<Props>()
</script>
<template>
<div
data-uipkge
data-slot="list-item-title"
:class="cn('text-foreground truncate text-sm leading-none font-medium', props.class)"
>
<slot />
</div>
</template>
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
interface Props {
class?: HTMLAttributes['class']
}
const props = defineProps<Props>()
</script>
<template>
<div
data-uipkge
data-slot="list-item-description"
:class="cn('text-muted-foreground line-clamp-1 text-xs', props.class)"
>
<slot />
</div>
</template>
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
interface Props {
class?: HTMLAttributes['class']
}
const props = defineProps<Props>()
</script>
<template>
<div
data-uipkge
data-slot="list-item-actions"
:class="cn('text-muted-foreground flex shrink-0 items-center gap-1.5 self-center', props.class)"
>
<slot />
</div>
</template>
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
const props = defineProps<{
class?: HTMLAttributes['class']
inset?: boolean
}>()
</script>
<template>
<li
data-uipkge
data-slot="list-subheader"
:class="
cn(
'text-muted-foreground px-2 py-1.5 text-xs font-semibold tracking-wide uppercase',
inset && 'pl-8',
props.class,
)
"
v-bind="$attrs"
>
<slot />
</li>
</template>
export { default as List } from './List.vue'
export { default as ListItem } from './ListItem.vue'
export { default as ListItemMedia } from './ListItemMedia.vue'
export { default as ListItemContent } from './ListItemContent.vue'
export { default as ListItemTitle } from './ListItemTitle.vue'
export { default as ListItemDescription } from './ListItemDescription.vue'
export { default as ListItemActions } from './ListItemActions.vue'
export { default as ListSubheader } from './ListSubheader.vue'
Raw manifest:https://uipkge.dev/r/vue/list.json