
3D Extruded Buildings & Urban Footprints
Real-world 3D building extrusions with dynamic sunlight shadows, terrain DEM elevations, and pitch/bearing camera controls.
Site footer collection: multi-column newsletter, oversized brand statement, dense developer grid, mega newsletter, single-row minimal, and dense sitemap variants — each spelled out inline so copy lives in one place.
Also available for React ->$pnpm dlx shadcn-vue@latest add https://uipkge.dev/r/vue/footer.json$npx shadcn-vue@latest add https://uipkge.dev/r/vue/footer.json$yarn dlx shadcn-vue@latest add https://uipkge.dev/r/vue/footer.json$bunx shadcn-vue@latest add https://uipkge.dev/r/vue/footer.jsonnpx shadcn-vue@latest add @uipkge/footerInstalls to:app/components/blocks/footer/Type aliases exported from this item's source. Use these to shape the data you pass in.
FooterColumninterface FooterColumn {
title: string
links: { label: string; href: string; badge?: string }[]
}<script setup lang="ts">
import { ref } from 'vue'
import { Boxes, Github, Linkedin, Twitter } from 'lucide-vue-next'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Separator } from '@/components/ui/separator'
const newsletter = ref('')
const subscribed = ref(false)
function subscribe() {
if (!newsletter.value) return
subscribed.value = true
newsletter.value = ''
}
</script>
<template>
<footer data-slot="footer-01" class="bg-background border-t">
<div class="mx-auto max-w-6xl px-6 py-16">
<div class="grid gap-10 lg:grid-cols-12">
<div class="space-y-4 lg:col-span-4">
<div class="flex items-center gap-2">
<div class="bg-primary text-primary-foreground flex size-8 items-center justify-center rounded-md">
<Boxes class="size-4" />
</div>
<span class="text-base font-semibold">Acme</span>
</div>
<p class="text-muted-foreground max-w-sm text-sm">Built on shadcn-vue. Get product updates monthly.</p>
<form class="flex max-w-sm gap-2" @submit.prevent="subscribe">
<Input v-model="newsletter" type="email" placeholder="[email protected]" required class="flex-1" />
<Button type="submit">Subscribe</Button>
</form>
<p v-if="subscribed" class="text-success text-xs">Thanks — check your inbox to confirm.</p>
</div>
<div class="grid grid-cols-2 gap-6 sm:grid-cols-4 lg:col-span-8">
<div class="space-y-3">
<h3 class="text-sm font-semibold">Product</h3>
<ul class="space-y-2">
<li>
<a href="#" class="text-muted-foreground hover:text-foreground text-sm transition-colors">Features</a>
</li>
<li>
<a href="#" class="text-muted-foreground hover:text-foreground text-sm transition-colors">Pricing</a>
</li>
<li>
<a href="#" class="text-muted-foreground hover:text-foreground text-sm transition-colors">Changelog</a>
</li>
<li>
<a href="#" class="text-muted-foreground hover:text-foreground text-sm transition-colors">Roadmap</a>
</li>
</ul>
</div>
<div class="space-y-3">
<h3 class="text-sm font-semibold">Company</h3>
<ul class="space-y-2">
<li>
<a href="#" class="text-muted-foreground hover:text-foreground text-sm transition-colors">About</a>
</li>
<li>
<a href="#" class="text-muted-foreground hover:text-foreground text-sm transition-colors">Blog</a>
</li>
<li>
<a href="#" class="text-muted-foreground hover:text-foreground text-sm transition-colors">Careers</a>
</li>
<li>
<a href="#" class="text-muted-foreground hover:text-foreground text-sm transition-colors">Contact</a>
</li>
</ul>
</div>
<div class="space-y-3">
<h3 class="text-sm font-semibold">Resources</h3>
<ul class="space-y-2">
<li>
<a href="#" class="text-muted-foreground hover:text-foreground text-sm transition-colors"
>Documentation</a
>
</li>
<li><a href="#" class="text-muted-foreground hover:text-foreground text-sm transition-colors">API</a></li>
<li>
<a href="#" class="text-muted-foreground hover:text-foreground text-sm transition-colors"
>Help center</a
>
</li>
<li>
<a href="#" class="text-muted-foreground hover:text-foreground text-sm transition-colors">Status</a>
</li>
</ul>
</div>
<div class="space-y-3">
<h3 class="text-sm font-semibold">Legal</h3>
<ul class="space-y-2">
<li>
<a href="#" class="text-muted-foreground hover:text-foreground text-sm transition-colors">Terms</a>
</li>
<li>
<a href="#" class="text-muted-foreground hover:text-foreground text-sm transition-colors">Privacy</a>
</li>
<li>
<a href="#" class="text-muted-foreground hover:text-foreground text-sm transition-colors">Security</a>
</li>
<li><a href="#" class="text-muted-foreground hover:text-foreground text-sm transition-colors">DPA</a></li>
</ul>
</div>
</div>
</div>
<Separator class="my-10" />
<div class="flex flex-wrap items-center justify-between gap-4">
<p class="text-muted-foreground text-xs">© 2026 Acme. All rights reserved.</p>
<div class="flex items-center gap-3">
<a href="#" aria-label="GitHub" class="text-muted-foreground hover:text-foreground transition-colors">
<Github class="size-4" />
</a>
<a href="#" aria-label="Twitter" class="text-muted-foreground hover:text-foreground transition-colors">
<Twitter class="size-4" />
</a>
<a href="#" aria-label="LinkedIn" class="text-muted-foreground hover:text-foreground transition-colors">
<Linkedin class="size-4" />
</a>
</div>
</div>
</div>
</footer>
</template>
<script setup lang="ts">
import { Github, Layers, Linkedin } from 'lucide-vue-next'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Separator } from '@/components/ui/separator'
const sections = [
{ heading: 'Platform', links: ['Metric layer', 'Governance', 'Alerting', 'Embedding'] },
{ heading: 'Company', links: ['About', 'Careers', 'Customers', 'Press kit'] },
{ heading: 'Resources', links: ['Documentation', 'Guides', 'Benchmarks', 'Status'] },
]
</script>
<template>
<footer data-slot="footer-brand-statement" class="border-border bg-background border-t">
<div class="mx-auto max-w-6xl px-6 py-16">
<a href="#top" class="flex w-fit items-center gap-2 font-semibold tracking-tight">
<Layers class="text-primary size-5" aria-hidden="true" />
Northwind
</a>
<!-- The statement is the only oversized type on the page. Keep it to one
sentence; at this size a second one stops being read. -->
<p class="mt-8 max-w-3xl text-2xl leading-tight font-medium tracking-tight text-balance sm:text-3xl">
Every number your company acts on should have one definition, one owner, and a history you can read.
</p>
<p class="text-muted-foreground mt-4 max-w-xl text-sm leading-relaxed">
That is the whole product. Everything else here is detail.
</p>
<Separator class="my-12" />
<div class="grid grid-cols-2 gap-8 sm:grid-cols-3">
<div v-for="section in sections" :key="section.heading">
<p class="text-muted-foreground/70 text-xs font-medium tracking-wide uppercase">{{ section.heading }}</p>
<ul class="mt-3 space-y-2">
<li v-for="link in section.links" :key="link">
<a href="#" class="text-muted-foreground hover:text-foreground text-sm transition-colors">{{ link }}</a>
</li>
</ul>
</div>
</div>
<Separator class="my-10" />
<div class="flex flex-col gap-4 sm:flex-row sm:items-center">
<div class="text-muted-foreground flex flex-wrap items-center gap-x-4 gap-y-2 text-xs">
<span>© 2026 Northwind Data, Inc.</span>
<a href="#privacy" class="hover:text-foreground transition-colors">Privacy</a>
<a href="#terms" class="hover:text-foreground transition-colors">Terms</a>
<a href="mailto:[email protected]" class="hover:text-foreground transition-colors">
[email protected]
</a>
</div>
<div class="flex items-center gap-2 sm:ml-auto">
<Badge variant="outline">SOC 2 Type II</Badge>
<Badge variant="outline">GDPR</Badge>
<Button variant="ghost" size="icon" class="size-8" aria-label="GitHub">
<Github class="size-4" aria-hidden="true" />
</Button>
<Button variant="ghost" size="icon" class="size-8" aria-label="LinkedIn">
<Linkedin class="size-4" aria-hidden="true" />
</Button>
</div>
</div>
</div>
</footer>
</template>
<script setup lang="ts">
import { Github, Globe, Twitter } from 'lucide-vue-next'
import { Button } from '@/components/ui/button'
interface FooterColumn {
title: string
links: { label: string; href: string; badge?: string }[]
}
const columns: FooterColumn[] = [
{
title: 'UI Primitives',
links: [
{ label: 'Button & Controls', href: '#' },
{ label: 'Dialog & Overlays', href: '#' },
{ label: 'Data Table & Virtual', href: '#' },
{ label: 'Charts & Sparklines', href: '#' },
{ label: 'Form Controls & Inputs', href: '#' },
{ label: 'KpiGrid & Metric Tiles', href: '#' },
],
},
{
title: 'Marketing Blocks',
links: [
{ label: 'Hero Sandboxes', href: '#' },
{ label: 'Feature Workbenches', href: '#' },
{ label: 'Pricing Matrix & ROI', href: '#' },
{ label: 'Social Proof Tickers', href: '#' },
{ label: 'Testimonial Masonry', href: '#' },
{ label: 'Interactive Demos', href: '#', badge: 'New' },
],
},
{
title: 'Ecosystem Templates',
links: [
{ label: 'HRMS Nuxt Template', href: '#' },
{ label: 'HMS Clinical Dashboard', href: '#' },
{ label: 'Shipment Tracking Live', href: '#' },
{ label: 'Fintech Banking Portal', href: '#' },
{ label: 'AI LLM Workspace', href: '#' },
],
},
{
title: 'Developer DX',
links: [
{ label: 'CLI Installation Guide', href: '#' },
{ label: 'Tailwind CSS v4 Tokens', href: '#' },
{ label: 'Dual-Framework Parity', href: '#' },
{ label: 'Reka UI Primitives', href: '#' },
{ label: 'Radix UI Primitives', href: '#' },
{ label: 'Changelog Timeline', href: '#' },
],
},
]
</script>
<template>
<footer data-slot="footer-developer-dense-grid" class="border-border bg-card text-foreground border-t">
<div class="mx-auto max-w-7xl space-y-12 px-4 py-12 sm:px-6 sm:py-16 lg:px-8">
<!-- Top Grid: Brand & Categorized Columns -->
<div class="grid grid-cols-2 gap-8 text-left md:grid-cols-6 lg:grid-cols-12">
<!-- Brand Info (4 Cols) -->
<div class="col-span-2 space-y-4 md:col-span-6 lg:col-span-4">
<div class="flex items-center gap-2 font-mono text-base font-bold tracking-tight">
<span
class="bg-primary text-primary-foreground flex size-6 items-center justify-center rounded-lg text-xs font-bold shadow-xs"
>
U
</span>
<span>UIPKGE.DEV</span>
</div>
<p class="text-muted-foreground max-w-sm text-xs leading-relaxed">
The open-source unbundled component registry where you own 100% of the source code. Calibrated tokens, pure
AST distribution, and mathematical dual-framework parity across Vue 3.5 and React 19.
</p>
<!-- Social links -->
<div class="flex items-center gap-2 pt-2">
<Button size="sm" variant="outline" class="size-8 rounded-lg p-0">
<Github class="size-3.5" />
</Button>
<Button size="sm" variant="outline" class="size-8 rounded-lg p-0">
<Twitter class="size-3.5" />
</Button>
<Button size="sm" variant="outline" class="size-8 rounded-lg p-0">
<Globe class="size-3.5" />
</Button>
</div>
</div>
<!-- 4 Links Columns (8 Cols total, 2 cols each) -->
<div v-for="(col, idx) in columns" :key="idx" class="col-span-1 space-y-3 md:col-span-3 lg:col-span-2">
<h4 class="text-foreground font-mono text-xs font-bold tracking-wider uppercase">
{{ col.title }}
</h4>
<ul class="text-muted-foreground space-y-2 font-mono text-xs">
<li v-for="(link, lIdx) in col.links" :key="lIdx">
<a :href="link.href" class="hover:text-foreground inline-flex items-center gap-1.5 transition-colors">
<span>{{ link.label }}</span>
<span
v-if="link.badge"
class="py-0.2 bg-primary/20 text-primary rounded px-1.5 text-xs font-bold uppercase"
>
{{ link.badge }}
</span>
</a>
</li>
</ul>
</div>
</div>
<!-- Bottom Operational Status & Copyright Bar -->
<div
class="border-border/80 text-muted-foreground flex flex-col items-center justify-between gap-4 border-t pt-8 text-center font-mono text-xs sm:flex-row sm:text-left"
>
<!-- Live System Telemetry Status -->
<div class="flex items-center gap-2.5">
<span class="relative flex size-2">
<span class="bg-success absolute inline-flex h-full w-full rounded-full opacity-75" />
<span class="bg-success relative inline-flex size-2 rounded-full" />
</span>
<span class="text-foreground font-medium">All Global Edge Nodes Operational</span>
<span class="text-muted-foreground/60">• Latency: 14ms (P99)</span>
</div>
<!-- Right Copyright & MIT Notice -->
<div class="flex items-center gap-3">
<span>MIT Licensed • Unbundled Code</span>
<span>© {{ new Date().getFullYear() }} UIPKGE</span>
</div>
</div>
</div>
</footer>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ArrowRight, Check, Github, Layers, Linkedin, Youtube } from 'lucide-vue-next'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Separator } from '@/components/ui/separator'
const sections = [
{ heading: 'Product', links: ['Metric layer', 'Dashboards', 'Alerting', 'Embedding', 'Changelog'] },
{ heading: 'Solutions', links: ['Finance', 'RevOps', 'Analytics', 'Engineering'] },
{ heading: 'Developers', links: ['Documentation', 'API reference', 'CLI', 'Status'] },
{ heading: 'Company', links: ['About', 'Careers', 'Customers', 'Press kit'] },
{ heading: 'Legal', links: ['Privacy', 'Terms', 'DPA', 'Subprocessors'] },
]
const email = ref('')
const submitted = ref(false)
function subscribe() {
if (!email.value.trim()) return
submitted.value = true
}
</script>
<template>
<footer data-slot="footer-mega-newsletter" class="border-border bg-background border-t">
<div class="mx-auto max-w-6xl px-6 py-14">
<div class="grid gap-12 lg:grid-cols-[22rem_1fr] lg:gap-16">
<div>
<a href="#top" class="flex items-center gap-2 font-semibold tracking-tight">
<Layers class="text-primary size-5" aria-hidden="true" />
Northwind
</a>
<p class="text-muted-foreground mt-3 max-w-sm text-sm leading-relaxed">
One certified metric layer between your warehouse and everything downstream.
</p>
<form class="mt-6" @submit.prevent="subscribe">
<label for="footer-email" class="text-sm font-medium">Monthly engineering notes</label>
<div class="mt-2 flex gap-2">
<Input
id="footer-email"
v-model="email"
type="email"
placeholder="[email protected]"
autocomplete="email"
class="max-w-64"
/>
<Button type="submit">
Subscribe
<ArrowRight class="ml-1.5 size-3.5" aria-hidden="true" />
</Button>
</div>
<!-- Status line reserves its own row, so confirming does not shift the columns. -->
<p class="text-muted-foreground mt-2 min-h-5 text-xs">
<span v-if="submitted" class="text-success inline-flex items-center gap-1.5">
<Check class="size-3" aria-hidden="true" />
Check your inbox to confirm.
</span>
<span v-else>One email a month. Unsubscribe in one click.</span>
</p>
</form>
</div>
<div class="grid grid-cols-2 gap-8 sm:grid-cols-3 lg:grid-cols-5">
<div v-for="section in sections" :key="section.heading">
<p class="text-muted-foreground/70 text-xs font-medium tracking-wide uppercase">{{ section.heading }}</p>
<ul class="mt-3 space-y-2">
<li v-for="link in section.links" :key="link">
<a href="#" class="text-muted-foreground hover:text-foreground text-sm transition-colors">{{ link }}</a>
</li>
</ul>
</div>
</div>
</div>
<Separator class="my-10" />
<div class="flex flex-col gap-4 sm:flex-row sm:items-center">
<p class="text-muted-foreground text-xs">© 2026 Northwind Data, Inc. All rights reserved.</p>
<Badge variant="outline" class="w-fit">SOC 2 Type II</Badge>
<div class="flex items-center gap-1 sm:ml-auto">
<Button variant="ghost" size="icon" class="size-8" aria-label="GitHub">
<Github class="size-4" aria-hidden="true" />
</Button>
<Button variant="ghost" size="icon" class="size-8" aria-label="LinkedIn">
<Linkedin class="size-4" aria-hidden="true" />
</Button>
<Button variant="ghost" size="icon" class="size-8" aria-label="YouTube">
<Youtube class="size-4" aria-hidden="true" />
</Button>
</div>
</div>
</div>
</footer>
</template>
<script setup lang="ts">
import { Github, Layers, Linkedin } from 'lucide-vue-next'
import { Button } from '@/components/ui/button'
import { Separator } from '@/components/ui/separator'
const links = [
{ label: 'Product', href: '#product' },
{ label: 'Pricing', href: '#pricing' },
{ label: 'Docs', href: '#docs' },
{ label: 'Privacy', href: '#privacy' },
{ label: 'Terms', href: '#terms' },
]
</script>
<template>
<footer data-slot="footer-minimal-row" class="border-border bg-background border-t">
<div class="mx-auto flex max-w-6xl flex-col gap-4 px-6 py-6 sm:flex-row sm:items-center">
<a href="#top" class="flex shrink-0 items-center gap-2 text-sm font-semibold tracking-tight">
<Layers class="text-primary size-4" aria-hidden="true" />
Northwind
</a>
<Separator orientation="vertical" class="hidden h-4 sm:block" />
<nav class="flex flex-wrap items-center gap-x-5 gap-y-2" aria-label="Footer">
<a
v-for="link in links"
:key="link.href"
:href="link.href"
class="text-muted-foreground hover:text-foreground text-sm transition-colors"
>
{{ link.label }}
</a>
</nav>
<div class="flex items-center gap-1 sm:ml-auto">
<Button variant="ghost" size="icon" class="size-8" aria-label="GitHub">
<Github class="size-4" aria-hidden="true" />
</Button>
<Button variant="ghost" size="icon" class="size-8" aria-label="LinkedIn">
<Linkedin class="size-4" aria-hidden="true" />
</Button>
<span class="text-muted-foreground ml-2 text-xs">© 2026 Northwind</span>
</div>
</div>
</footer>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { Github, Layers, Linkedin } from 'lucide-vue-next'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Separator } from '@/components/ui/separator'
const sections = [
{ heading: 'Platform', links: ['Metric layer', 'Dashboards', 'Alerting', 'Embedding', 'API', 'CLI'] },
{ heading: 'Solutions', links: ['Finance', 'RevOps', 'Analytics', 'Engineering', 'Healthcare', 'Retail'] },
{ heading: 'Developers', links: ['Documentation', 'API reference', 'SDKs', 'Webhooks', 'Status', 'Changelog'] },
{ heading: 'Resources', links: ['Guides', 'Benchmarks', 'Customers', 'Blog', 'Webinars', 'Glossary'] },
{ heading: 'Company', links: ['About', 'Careers', 'Press kit', 'Partners', 'Contact', 'Security'] },
{ heading: 'Legal', links: ['Privacy', 'Terms', 'DPA', 'Subprocessors', 'SLA', 'Accessibility'] },
]
const region = ref('eu')
const currency = ref('eur')
</script>
<template>
<footer data-slot="footer-sitemap-dense" class="border-border bg-background border-t">
<div class="mx-auto max-w-6xl px-6 py-12">
<div class="grid grid-cols-2 gap-x-6 gap-y-8 sm:grid-cols-3 lg:grid-cols-6">
<div v-for="section in sections" :key="section.heading">
<p class="text-muted-foreground/70 text-xs font-medium tracking-wide uppercase">{{ section.heading }}</p>
<ul class="mt-3 space-y-1.5">
<li v-for="link in section.links" :key="link">
<a href="#" class="text-muted-foreground hover:text-foreground text-sm transition-colors">{{ link }}</a>
</li>
</ul>
</div>
</div>
<Separator class="my-10" />
<div class="flex flex-col gap-6 lg:flex-row lg:items-center">
<a href="#top" class="flex shrink-0 items-center gap-2 text-sm font-semibold tracking-tight">
<Layers class="text-primary size-4" aria-hidden="true" />
Northwind
</a>
<div class="flex flex-wrap items-center gap-2">
<Select v-model="region">
<SelectTrigger class="h-8 w-40 text-xs" aria-label="Data region">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="eu">EU (Frankfurt)</SelectItem>
<SelectItem value="us">US (Virginia)</SelectItem>
<SelectItem value="au">AU (Sydney)</SelectItem>
</SelectContent>
</Select>
<Select v-model="currency">
<SelectTrigger class="h-8 w-28 text-xs" aria-label="Currency">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="eur">EUR €</SelectItem>
<SelectItem value="usd">USD $</SelectItem>
<SelectItem value="gbp">GBP £</SelectItem>
</SelectContent>
</Select>
</div>
<div class="flex flex-wrap items-center gap-3 lg:ml-auto">
<!-- Status dot is a live indicator, so it carries its own text for
screen readers rather than relying on colour alone. -->
<a href="#status" class="hover:bg-muted flex items-center gap-2 rounded-md px-2 py-1 transition-colors">
<span class="bg-success size-2 rounded-full" aria-hidden="true" />
<span class="text-muted-foreground text-xs">All systems operational</span>
</a>
<Badge variant="outline">SOC 2</Badge>
<Badge variant="outline">GDPR</Badge>
<Button variant="ghost" size="icon" class="size-8" aria-label="GitHub">
<Github class="size-4" aria-hidden="true" />
</Button>
<Button variant="ghost" size="icon" class="size-8" aria-label="LinkedIn">
<Linkedin class="size-4" aria-hidden="true" />
</Button>
</div>
</div>
<p class="text-muted-foreground mt-6 text-xs">© 2026 Northwind Data, Inc. All rights reserved.</p>
</div>
</footer>
</template>
Raw manifest:https://uipkge.dev/r/vue/footer.json