hexui
blockfooter-2source: ok · hit
Footer-2
A simple footer with social media buttons and language switching
npx shadcn add "https://hexui.sh/r/footer-2.json"free
Where it goes
footer
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Dependencies
Bundle weight · lighticonslucide-react
Registry dependencies
buttonselect
Files5 · source cached 10/8/2026, 1:42:54 PM
registry/blocks/footer/footer-2/source/app/footer/page.tsx
import { SiteFooter } from "../../components/site-footer"
export default function Page() {
return (
<div className="flex flex-col w-full min-h-dvh items-center justify-center">
<SiteFooter />
</div>
)
}
registry/blocks/footer/footer-2/source/components/site-footer.tsx
import Link from "next/link"
import { Command } from "lucide-react"
import { NavLinks } from "./nav-links"
import { LanguageSelect } from "./language-select"
import { SocialLinks } from "./social-links"
import { SiX, SiInstagram, SiFacebook, SiGithub, SiBluesky } from "@icons-pack/react-simple-icons"
const SOCIAL_LINKS = [
{ icon: SiX, label: "X" },
{ icon: SiInstagram, label: "Instagram" },
{ icon: SiFacebook, label: "Facebook" },
{ icon: SiGithub, label: "GitHub" },
{ icon: SiBluesky, label: "Bluesky" },
]
const footerSections = [
{
title: "Product",
links: [
{ label: "Features", href: "#" },
{ label: "Pricing", href: "#" },
{ label: "Case Studies", href: "#" },
{ label: "Changelog", href: "#" },
],
},
{
title: "Company",
links: [
{ label: "About Us", href: "#" },
{ label: "Careers", href: "#" },
{ label: "Blog", href: "#" },
{ label: "Contact", href: "#" },
],
},
{
title: "Resources",
links: [
{ label: "Documentation", href: "#" },
{ label: "API Reference", href: "#" },
{ label: "Guides", href: "#" },
{ label: "Support", href: "#" },
],
},
{
title: "Legal",
links: [
{ label: "Terms of Service", href: "#" },
{ label: "Privacy Policy", href: "#" },
{ label: "Cookie Settings", href: "#" },
],
},
]
export function SiteFooter() {
return (
<footer className="w-full border-t border-border">
<div className="mx-auto max-w-7xl px-6 py-12 md:px-8 md:pb-16 md:pt-12">
<div className="flex flex-col xl:flex-row gap-8 sm:gap-12">
<div className="flex flex-col h-full justify-between space-y-6 xl:w-1/4 xl:max-w-sm">
<div className="flex flex-col space-y-4">
<Link href="#" className="flex items-center gap-2">
<Command />
<span className="text-xl font-semibold text-foreground">Acme Inc.</span>
</Link>
<SocialLinks links={SOCIAL_LINKS} />
</div>
<LanguageSelect />
<div className="flex flex-col gap-4">
<Link href="#" className="text-xs text-muted-foreground transition-colors hover:text-foreground">
Cookie Setting
</Link>
<p className="text-xs text-muted-foreground">
© {new Date().getFullYear()} Acme Inc. All rights reserved.
</p>
</div>
</div>
<NavLinks sections={footerSections} />
</div>
</div>
</footer>
)
}
registry/blocks/footer/footer-2/source/components/language-select.tsx
"use client"
import { useState } from "react"
import { Languages } from "lucide-react"
import { Select, SelectContent, SelectItem, SelectTrigger } from "@/components/ui/select"
const items = [
{ label: "English", value: "en" },
{ label: "日本語", value: "ja" },
{ label: "한국어", value: "ko" },
{ label: "中文", value: "zh" },
];
export function LanguageSelect() {
const [selectedValue, setSelectedValue] = useState("en")
// Derive the human-readable label shown in the trigger from the selected value.
const selectedLabel = items.find((item) => item.value === selectedValue)?.label || "English"
return (
<Select value={selectedValue} onValueChange={(value) => {
if (value) setSelectedValue(value)
}}>
<SelectTrigger className="h-7 xl:h-9 w-40 rounded-md border-border/70 bg-background/80 px-3 shadow-xs">
<div className="flex min-w-0 items-center gap-2">
<Languages className="size-4 shrink-0 text-muted-foreground" />
<span className="truncate text-sm">{selectedLabel}</span>
</div>
</SelectTrigger>
{/* Opens upward: this select sits near the page bottom, so a downward popup would overflow. */}
<SelectContent side="top" sideOffset={8} align="start" alignItemWithTrigger={false}>
{items.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectContent>
</Select>
)
}registry/blocks/footer/footer-2/source/components/nav-links.tsx
import Link from "next/link"
type FooterLinkItem = {
label: string
href: string
}
type FooterSection = {
title: string
links: FooterLinkItem[]
}
type NavLinksProps = {
sections: FooterSection[]
}
export function NavLinks({ sections }: NavLinksProps) {
return (
<div className="grid gap-6 sm:gap-8 grid-cols-2 xl:w-2/3 xl:flex-1 xl:grid-cols-4">
{sections.map((section) => (
<div key={section.title}>
<h3 className="text-sm font-semibold text-foreground">{section.title}</h3>
{/* role="list" keeps list semantics; Tailwind preflight strips default list styles. */}
<ul role="list" className="mt-4 space-y-2 sm:space-y-3">
{section.links.map((item) => (
<li key={item.label}>
<Link
href={item.href}
className="text-sm text-muted-foreground transition-colors hover:text-foreground"
>
{item.label}
</Link>
</li>
))}
</ul>
</div>
))}
</div>
)
}
registry/blocks/footer/footer-2/source/components/social-links.tsx
import { Button } from "@/components/ui/button"
interface SocialLink {
icon: React.ComponentType<{ className?: string }>
label: string
}
interface SocialLinksProps {
links: SocialLink[]
}
export function SocialLinks({ links }: SocialLinksProps) {
return (
<div className="flex gap-0.5 text-muted-foreground -ml-1.5">
{links.map((link) => {
const Icon = link.icon
return (
<Button
key={link.label}
variant={"ghost"}
size="icon"
className="size-8 [&_svg]:size-4 xl:size-10 xl:[&_svg]:size-4.5"
aria-label={link.label}
>
<Icon className="size-5" />
</Button>
)
})}
</div>
)
}