
3D Extruded Buildings & Urban Footprints
Real-world 3D building extrusions with dynamic sunlight shadows, terrain DEM elevations, and pitch/bearing camera controls.
icon-boxuiIcon container primitive with calibrated sizes (2xs to xl), surface variants (solid, outline, subtle, primary, muted), and 2.5D layered IconStack for empty states and feature hero cards.
Also available for React ->$pnpm dlx shadcn-vue@latest add https://uipkge.dev/r/vue/icon-box.json$npx shadcn-vue@latest add https://uipkge.dev/r/vue/icon-box.json$yarn dlx shadcn-vue@latest add https://uipkge.dev/r/vue/icon-box.json$bunx shadcn-vue@latest add https://uipkge.dev/r/vue/icon-box.jsonnpx shadcn-vue@latest add @uipkge/icon-boxInstalls to:app/components/ui/icon-box/| Name | Type / Values | Default | Required |
|---|---|---|---|
icon | Component | — | optional |
variant | | 'primary' | 'primary' | optional |
shape | 'rounded''circle''square' | 'rounded' | optional |
size | '2xs''xs''sm''md''lg''xl' | 'md' | optional |
class | string | — | optional |
iconClass | string | — | optional |
<script setup lang="ts">
import type { Component } from 'vue'
import { cn } from '@/lib/utils'
type Variant =
| 'primary'
| 'muted'
| 'outline'
| 'solid'
| 'subtle'
| 'destructive'
| 'success'
| 'warning'
| 'ghost'
| 'custom'
type Size = '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'
type Shape = 'rounded' | 'circle' | 'square'
const props = withDefaults(
defineProps<{
icon?: Component
variant?: Variant
shape?: Shape
size?: Size
class?: string
iconClass?: string
}>(),
{
variant: 'primary',
shape: 'rounded',
size: 'md',
},
)
const variantClasses: Record<Variant, string> = {
primary: 'bg-primary/10 text-primary',
muted: 'bg-muted text-muted-foreground',
outline: 'border border-border bg-background text-foreground shadow-xs',
solid: 'bg-foreground text-background shadow-xs',
subtle: 'bg-accent text-accent-foreground',
destructive: 'bg-destructive/10 text-destructive',
success: 'bg-success/15 text-success',
warning: 'bg-warning/15 text-warning',
ghost: 'text-muted-foreground hover:bg-accent hover:text-foreground',
custom: '',
}
const shapeClasses: Record<Shape, string> = {
rounded: 'rounded-lg',
circle: 'rounded-full',
square: 'rounded-none',
}
const sizeClasses: Record<Size, string> = {
'2xs': 'size-6 p-1',
xs: 'size-7 p-1.5',
sm: 'size-8 p-1.5',
md: 'size-9 p-2',
lg: 'size-11 p-2.5',
xl: 'size-14 p-3.5',
}
const iconSizes: Record<Size, string> = {
'2xs': 'size-3',
xs: 'size-3.5',
sm: 'size-4',
md: 'size-4.5',
lg: 'size-6',
xl: 'size-7',
}
</script>
<template>
<div
data-uipkge
data-slot="icon-box"
:data-variant="variant"
:data-size="size"
:data-shape="shape"
:class="
cn(
'inline-flex shrink-0 items-center justify-center transition-colors',
variantClasses[variant],
shapeClasses[shape],
sizeClasses[size],
props.class,
)
"
>
<slot>
<component :is="icon" v-if="icon" :class="cn(iconSizes[size], props.iconClass)" aria-hidden="true" />
</slot>
</div>
</template>
<script setup lang="ts">
import type { Component, HTMLAttributes } from 'vue'
import { cn } from '@/lib/utils'
type Variant = 'primary' | 'muted' | 'destructive' | 'success' | 'warning'
type Size = 'sm' | 'md' | 'lg' | 'xl'
interface Props {
icon?: Component
variant?: Variant
size?: Size
class?: HTMLAttributes['class']
iconClass?: HTMLAttributes['class']
}
const props = withDefaults(defineProps<Props>(), {
variant: 'primary',
size: 'md',
})
const sizeMap: Record<Size, { root: string; base: string; icon: string }> = {
sm: { root: 'size-10', base: 'size-8 rounded-lg', icon: 'size-4' },
md: { root: 'size-14', base: 'size-11 rounded-xl', icon: 'size-5' },
lg: { root: 'size-18', base: 'size-14 rounded-2xl', icon: 'size-7' },
xl: { root: 'size-24', base: 'size-18 rounded-3xl', icon: 'size-9' },
}
const variantMap: Record<Variant, { back: string; front: string; text: string }> = {
primary: {
back: 'bg-primary/20 border-primary/30',
front: 'bg-background border-primary/20 shadow-primary/10',
text: 'text-primary',
},
muted: {
back: 'bg-muted border-border',
front: 'bg-card border-border shadow-black/5',
text: 'text-muted-foreground',
},
destructive: {
back: 'bg-destructive/20 border-destructive/30',
front: 'bg-background border-destructive/20 shadow-destructive/10',
text: 'text-destructive',
},
success: {
back: 'bg-success/20 border-success/30',
front: 'bg-background border-success/20 shadow-success/10',
text: 'text-success',
},
warning: {
back: 'bg-warning/20 border-warning/30',
front: 'bg-background border-warning/20 shadow-warning/10',
text: 'text-warning',
},
}
</script>
<template>
<div
data-uipkge
data-slot="icon-stack"
:data-variant="variant"
:data-size="size"
:class="cn('relative inline-flex items-center justify-center select-none', sizeMap[size].root, props.class)"
>
<!-- Background offset sheet -->
<div
aria-hidden="true"
:class="
cn(
'absolute inset-0 m-auto rotate-6 border transition-transform duration-300',
sizeMap[size].base,
variantMap[variant].back,
)
"
/>
<!-- Top elevated card -->
<div
:class="
cn(
'relative z-10 flex items-center justify-center border shadow-md transition-transform duration-300 group-hover:-translate-y-0.5',
sizeMap[size].base,
variantMap[variant].front,
variantMap[variant].text,
)
"
>
<slot>
<component :is="icon" v-if="icon" :class="cn(sizeMap[size].icon, props.iconClass)" aria-hidden="true" />
</slot>
</div>
</div>
</template>
export { default as IconBox } from './IconBox.vue'
export { default as IconStack } from './IconStack.vue'
Raw manifest:https://uipkge.dev/r/vue/icon-box.json