Footer
Footer with links and optional social actions.
Installation
CLI
npx shadcn@latest add "https://ui.xaclabs.dev/r/footer.json"Usage
import { Footer } from "@/registry/components/footer"View component source
footer.tsx
tsx"use client"
import { Github, Linkedin, Twitter } from "lucide-react"
import { motion } from "motion/react"
import { resolveLink } from "@/lib/utils"
import { Container } from "@/registry/components/container"
export interface FooterMenu {
title: string
href?: string
children?: FooterMenu[]
}
export interface Socials {
github?: string
linkedin?: string
twitter?: string
}
export interface FooterProps {
socials?: Socials
footer: {
title: string
description: string
menus: FooterMenu[]
copyright: string
}
}
export function Footer({ footer, socials }: FooterProps) {
const { title, description, copyright, menus } = footer
return (
<motion.footer
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
className="my-12"
>
<Container className="border-t py-12">
<div className="grid grid-cols-1 md:grid-cols-4 gap-24">
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.1 }}
className="md:col-span-1 space-y-4"
>
<h1 className="text-lg font-semibold tracking-tight">{title}</h1>
<p className="text-sm leading-relaxed">{description}</p>
<div className="flex items-center space-x-4">
{socials?.github && (
<a
href={socials.github}
target="_blank"
rel="noopener noreferrer"
className="hover:text-muted-foreground transition-colors"
>
<Github className="h-5 w-5" />
</a>
)}
{socials?.linkedin && (
<a
href={socials.linkedin}
target="_blank"
rel="noopener noreferrer"
className="hover:text-muted-foreground transition-colors"
>
<Linkedin className="h-5 w-5" />
</a>
)}
{socials?.twitter && (
<a
href={socials.twitter}
target="_blank"
rel="noopener noreferrer"
className="hover:text-muted-foreground transition-colors"
>
<Twitter className="h-5 w-5" />
</a>
)}
</div>
<p className="text-sm">
Copyright {copyright} © {new Date().getFullYear()}
</p>
</motion.div>
<div className="md:col-span-3">
<nav className="flex flex-wrap gap-x-16 gap-y-8 justify-center">
{menus.map(({ title, children }, index) => (
<motion.div
key={title}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.2 + index * 0.1 }}
className="space-y-4"
>
<h3 className="text-lg font-semibold tracking-tight">
{title}
</h3>
<ul className="space-y-2">
{children?.map(({ title, href }) => {
const resolvedHref = resolveLink(href || "")
const isExternal = resolvedHref.startsWith("http")
return (
<li key={title}>
<a
href={resolvedHref}
target={isExternal ? "_blank" : undefined}
rel={isExternal ? "noopener noreferrer" : undefined}
className="text-sm hover:text-muted-foreground transition-colors"
>
{title}
</a>
</li>
)
})}
</ul>
</motion.div>
))}
</nav>
</div>
</div>
</Container>
</motion.footer>
)
}