Files
import { Fragment, type ReactNode } from "react";
export interface Footer03Link {
label: string;
href: string;
external?: boolean;
}
const SOCIAL_LINKS: Footer03Link[] = [
{
label: "GitHub",
href: "#",
external: true,
},
{
label: "X / Twitter",
href: "#",
external: true,
},
];
const EXPLORE_LINKS: Footer03Link[] = [
{ label: "Components", href: "#" },
{ label: "Blocks", href: "#" },
];
const LEGAL_LINKS: Footer03Link[] = [
{ label: "Terms", href: "#" },
{ label: "Privacy", href: "#" },
];
function ArrowIcon() {
return (
<svg
aria-hidden="true"
className="size-3 text-blue-500 transition-transform duration-200 ease-out group-hover:translate-x-0.5 group-hover:-translate-y-0.5 motion-reduce:transition-none"
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
>
<path d="M7 17 17 7M8 7h9v9" />
</svg>
);
}
function Mark() {
return (
<svg
aria-hidden="true"
className="size-full text-blue-500"
viewBox="0 0 24 23"
>
<path
d="M12.0139 14.2848C10.0072 14.2848 8.38019 12.6853 8.38019 10.7133C8.38019 8.74133 10.0072 7.14184 12.0139 7.14184C14.0207 7.14184 15.6458 8.74133 15.6458 10.7133C15.6458 12.6853 14.0188 14.2848 12.0139 14.2848ZM23.7677 15.174C23.5257 16.107 22.9021 16.9415 21.9732 17.4326C21.2509 17.8161 20.4542 17.9329 19.7002 17.8197C18.5703 17.6499 17.4161 17.8197 16.409 18.3547L16.221 18.4552C15.2157 18.9883 14.4339 19.8428 13.9592 20.8654C13.6428 21.5464 13.1048 22.138 12.3825 22.5215C11.9525 22.7497 11.4964 22.883 11.0366 22.9305C9.72795 23.1879 8.32621 22.726 7.45129 21.6213C6.94681 20.984 6.69734 20.2299 6.68804 19.4813C6.67314 18.3584 6.30827 17.2665 5.60833 16.3791L5.47617 16.2129C4.77437 15.3256 3.78775 14.7102 2.68572 14.4181C1.94855 14.2227 1.26908 13.7991 0.764598 13.1618C-0.462158 11.6098 -0.182909 9.37492 1.38823 8.16069C2.95751 6.94829 5.1988 7.19479 6.45534 8.72125C6.98774 9.36944 7.2521 10.14 7.26513 10.9105C7.28375 12.07 7.68769 13.1892 8.41182 14.104C9.01869 14.8709 9.82287 15.4588 10.7369 15.8076C10.8933 15.8112 11.0478 15.824 11.2004 15.8459C12.3657 16.0194 13.5571 15.8131 14.594 15.2616C15.5694 14.7449 16.3606 13.9488 16.8632 12.9811C16.9004 12.8478 16.9433 12.7163 16.9954 12.5885C17.4161 11.5441 17.5036 10.3974 17.1983 9.31467L17.1406 9.11016C16.8353 8.0274 16.1633 7.08706 15.2549 6.406C15.2493 6.40052 15.2418 6.39687 15.2362 6.39139C14.3334 5.73224 13.25 5.3561 12.1237 5.3561H11.9097C10.7667 5.3561 9.66652 5.74319 8.75436 6.42243C8.14564 6.87525 7.38985 7.14366 6.56705 7.14366C4.56776 7.14366 2.94449 5.55512 2.93518 3.59045C2.92402 1.6276 4.49699 0.0390573 6.49443 0.000713257C7.34143 -0.0157199 8.1233 0.252688 8.74877 0.714643C9.69071 1.41214 10.8393 1.78645 12.0177 1.78645C13.196 1.78645 14.3445 1.41214 15.2865 0.714643C15.912 0.252688 16.6938 -0.0157199 17.5408 0.000713257H17.5427C17.848 0.00619098 18.1496 0.0500127 18.4381 0.126701C18.4474 0.128527 18.4548 0.132179 18.4641 0.134004C18.5088 0.146786 18.5535 0.159567 18.5982 0.174174C18.6317 0.18513 18.6652 0.196085 18.6968 0.208867C18.7173 0.21617 18.7359 0.221648 18.7546 0.228952C18.8048 0.247211 18.8532 0.267296 18.9016 0.287381C18.9035 0.287381 18.9072 0.289207 18.9091 0.291032C20.026 0.762116 20.8432 1.76637 21.0461 2.98059C21.048 2.99155 21.0498 3.0025 21.0517 3.01529C21.0554 3.04085 21.0591 3.06641 21.0629 3.0938C21.0684 3.1358 21.074 3.17779 21.0778 3.21979C21.0778 3.22527 21.0778 3.22892 21.0778 3.23439C21.1317 3.77852 21.0554 4.31351 20.8711 4.8065C20.8692 4.81198 20.8656 4.81928 20.8637 4.82476C20.8563 4.84302 20.8488 4.8631 20.8413 4.88136C20.4095 5.96047 20.3499 7.14731 20.6626 8.26477C20.9772 9.38222 21.6493 10.37 22.5838 11.0767C23.2037 11.5459 23.6765 12.216 23.885 13.0231C24.0786 13.7681 24.0208 14.5148 23.764 15.1795L23.7677 15.174Z"
fill="currentColor"
/>
</svg>
);
}
function FooterLink({ link }: { link: Footer03Link }) {
return (
<a
className="group flex w-fit items-center gap-1.5 font-medium text-sm text-white/60 transition-colors duration-150 ease-out hover:text-white sm:text-base"
href={link.href}
rel={link.external ? "noreferrer" : undefined}
target={link.external ? "_blank" : undefined}
>
{link.label}
{link.external ? <ArrowIcon /> : null}
</a>
);
}
function ActionLink({
link,
primary = false,
}: {
link: Footer03Link;
primary?: boolean;
}) {
return (
<a
className={
primary
? "flex h-12 items-center rounded-full bg-blue-500 px-6 font-semibold text-sm text-white shadow-[inset_0_1px_0_0_rgba(255,255,255,0.25)] transition-colors duration-150 ease-out hover:bg-blue-600"
: "flex h-12 items-center rounded-full bg-white/[0.08] px-6 font-semibold text-sm text-white shadow-[inset_0_1px_0_0_rgba(255,255,255,0.14),inset_0_-1px_2px_0_rgba(0,0,0,0.3)] backdrop-blur-2xl transition-colors duration-150 ease-out hover:bg-white/[0.12]"
}
href={link.href}
rel={link.external ? "noreferrer" : undefined}
target={link.external ? "_blank" : undefined}
>
{link.label}
</a>
);
}
export function Footer03() {
return (
<footer className="relative w-full p-2.5 pt-20">
<div className="relative w-full overflow-visible rounded-[45px] bg-neutral-950 px-8 pt-28 pb-10 text-white shadow-[inset_0_1px_0_0_rgba(255,255,255,0.08)] sm:px-12">
<div className="absolute top-0 left-1/2 size-28 -translate-x-1/2 -translate-y-1/2 sm:size-36">
<Mark />
</div>
<div className="mx-auto grid w-full max-w-6xl grid-cols-1 items-center gap-12 md:grid-cols-[1fr_auto_1fr] md:items-start">
<nav
aria-label="Social links"
className="order-2 flex flex-col gap-2 md:order-1 md:pt-4"
>
<h2 className="mb-1 font-bold text-lg text-white tracking-tight">
Socials
</h2>
{SOCIAL_LINKS.map((link) => (
<FooterLink key={`${link.label}-${link.href}`} link={link} />
))}
</nav>
<div className="order-1 flex flex-col items-center gap-4 text-center md:order-2">
<h2 className="font-bold text-5xl tracking-tight sm:text-6xl">
Tent UI
</h2>
<p className="max-w-xl text-balance font-medium text-white/60 sm:text-lg">
A collection of beautifully crafted components and blocks for your
next project.
</p>
<div className="mt-2 flex flex-wrap items-center justify-center gap-3">
<ActionLink
link={{ label: "Browse components", href: "#" }}
primary
/>
<ActionLink
link={{
label: "Star on GitHub",
href: "#",
external: true,
}}
/>
</div>
</div>
<nav
aria-label="Explore"
className="order-3 flex flex-col gap-2 md:items-end md:pt-4"
>
<h2 className="mb-1 font-bold text-lg text-white tracking-tight">
Explore
</h2>
{EXPLORE_LINKS.map((link) => (
<FooterLink key={`${link.label}-${link.href}`} link={link} />
))}
</nav>
</div>
<div className="mx-auto mt-12 flex w-full max-w-6xl flex-wrap items-center justify-end gap-2.5">
{LEGAL_LINKS.map((link) => (
<Fragment key={`${link.label}-${link.href}`}>
<a
className="font-medium text-white/50 text-xs transition-colors duration-150 ease-out hover:text-white"
href={link.href}
rel={link.external ? "noreferrer" : undefined}
target={link.external ? "_blank" : undefined}
>
{link.label}
</a>
<span aria-hidden="true" className="text-white/25 text-xs">
·
</span>
</Fragment>
))}
<span className="font-medium text-white/50 text-xs">
© {new Date().getFullYear()} Tent UI
</span>
</div>
</div>
</footer>
);
}
A rounded dark footer with an overlapping brand mark, centered calls to action, and compact navigation
@tentui/footer-03
Files
import { RssIcon } from "lucide-react";
import type { Route } from "next";
import Link from "next/link";
import type { ReactNode } from "react";
import { cn } from "@/lib/utils";
export interface Footer02Link {
label: string;
href: string;
external?: boolean;
icon?: ReactNode;
}
export interface Footer02LinkGroup {
title: string;
links: Footer02Link[];
}
export interface Footer02Props {
brandName?: string;
brandHref?: string;
brandMark?: ReactNode;
copyright?: string;
linkGroups?: Footer02LinkGroup[];
socialLinks?: Footer02Link[];
wordmark?: ReactNode;
className?: string;
}
const DEFAULT_LINK_GROUPS: Footer02LinkGroup[] = [
{
title: "Components",
links: [
{ label: "3D Button", href: "#" },
{ label: "Animated Tabs", href: "#" },
{ label: "Copy Button", href: "#" },
{ label: "Email Dock", href: "#" },
{ label: "World Map", href: "#" },
],
},
{
title: "Shadcn Compatible Blocks",
links: [
{ label: "Hero", href: "#" },
{ label: "Pricing", href: "#" },
{ label: "FAQ", href: "#" },
{ label: "Footer", href: "#" },
],
},
{
title: "Legal",
links: [
{ label: "License", href: "#" },
{ label: "Terms of Service", href: "#" },
{ label: "Privacy", href: "#" },
{ label: "Copyright", href: "#" },
],
},
];
const DEFAULT_SOCIAL_LINKS: Footer02Link[] = [
{
label: "X (formerly Twitter)",
href: "https://x.com",
external: true,
},
{
label: "RSS",
href: "#",
icon: <RssIcon aria-hidden="true" className="size-3" />,
},
];
const GRAIN_BACKGROUND =
"url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E\")";
const EXTERNAL_URL_PATTERN = /^https?:\/\//;
const MARKER_POSITION_CLASSES = {
"top-left": "-top-[3px] -left-1",
"top-right": "-top-[3px] -right-1",
"bottom-left": "-bottom-[3px] -left-1",
"bottom-right": "-right-1 -bottom-[3px]",
} as const;
function Grain({ className }: { className?: string }) {
return (
<div
aria-hidden="true"
className={cn(
"pointer-events-none absolute inset-0 -z-10 opacity-10",
className,
)}
style={{
backgroundImage: GRAIN_BACKGROUND,
backgroundRepeat: "repeat",
}}
/>
);
}
function GridMarker({
position,
className,
}: {
position: "top-left" | "top-right" | "bottom-left" | "bottom-right";
className?: string;
}) {
return (
<span
aria-hidden="true"
className={cn(
"absolute z-10 size-1.5 rotate-45 border border-primary-foreground/25 bg-background",
MARKER_POSITION_CLASSES[position],
className,
)}
/>
);
}
function ArrowUpRight() {
return (
<svg
aria-hidden="true"
className="size-2.5"
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.5"
viewBox="0 0 12 12"
>
<path d="M3.5 8.5 8.5 3.5m0 0h-4m4 0v4" />
</svg>
);
}
function FooterLinkList({ links }: { links: Footer02Link[] }) {
return (
<ul className="mt-5 flex flex-col gap-1.5 font-mono text-[12px] text-primary-foreground/65 tracking-tight">
{links.map((link) => {
const external = link.external ?? EXTERNAL_URL_PATTERN.test(link.href);
return (
<li key={`${link.label}-${link.href}`}>
<Link
className="inline-flex items-center gap-1 transition-colors hover:text-primary-foreground"
href={link.href as Route}
rel={external ? "noreferrer" : undefined}
target={external ? "_blank" : undefined}
>
{link.icon}
{link.label}
{external ? <ArrowUpRight /> : null}
</Link>
</li>
);
})}
</ul>
);
}
function TentMark() {
return (
<svg
aria-hidden="true"
className="h-5 w-auto text-white"
fill="none"
viewBox="0 0 150 128"
>
<path
d="M10 128H0v-20.851h10V128Zm100-116.996h10V21.43h10v10.425h10V42.57h9.722v52.706H140v11.873h-10V74.715h-10v31.855h-10V128h-10V74.715H90v31.855H80V128H70v-21.43H60V74.715H50V128H40v-21.43H30V74.715H20v32.434H10V95.276H.278V42.57H10V31.855h10V21.43h10V11.004h10V0h70v11.004ZM30 42.86h10V31.855H30V42.86Zm80 0h10V31.855h-10V42.86ZM150 128h-10v-20.851h10V128Z"
fill="currentColor"
/>
</svg>
);
}
function TentWordmark() {
return (
<svg
aria-hidden="true"
className="h-auto w-full text-primary-foreground/30"
fill="none"
viewBox="0 0 929 258"
>
<path
className="fill-current/10"
d="M1 129h64v64H1zM161 193h64v64h-64zM161 1h64v64h-64zM705 1h64v64h-64zM705 193h64v64h-64zM864 129h64v64h-64z"
/>
<path
className="stroke-current/40"
d="M1 65h947M1 1h947M1 257h947M1 193h947M65 1v256M864 1v256"
strokeDasharray="8 4"
strokeLinecap="square"
/>
<g fill="currentColor" fillOpacity=".3" transform="translate(65 65)">
<path d="M149.5 107.649V127.5h-9v-19.851h9Zm-40-107.149v11.005h10V21.93h10v10.425h10V43.07h9.723v51.706H139.5v11.873h-9V74.215h-11v31.855h-10v21.43h-9V74.215h-11v31.855h-10v21.43h-9v-21.43h-10V74.215h-11v53.285h-9v-21.43h-10V74.215h-11v32.434h-9V94.776H.777V43.07H10.5V32.355h10V21.93h10V11.505h10V.5h69ZM9.5 107.649V127.5h-9v-19.851h9Zm20-64.29h11V31.356h-11v12.004Zm80 0h11V31.356h-11v12.004Z" />
<path d="M330.175 41.279v16.983h17.201v19.476h-20.071V60.941h-47.989v13.931h34.031v19.849h-34.031v14.117h47.989V91.668h20.071v19.85h-17.201V128.5h-53.728v-16.796h-17.015V58.262h17.015V41.279h53.728Zm109.556 0v16.983h17.202V128.5h-20.072V60.941h-47.988V128.5h-19.885V41.279h70.743ZM507.771 7.5v33.779h51.232v19.662h-51.232v47.897h48.363V91.855h19.885v19.849h-17.016V128.5h-54.101v-16.796h-16.828V60.941h-17.015V41.279h17.015V7.5h19.697Zm147.142 0v101.338h47.988V7.5h20.072v104.204h-17.202V128.5h-53.727v-16.796h-17.015V7.5h19.884ZM798.5 7.5v19.85h-17.202v81.488H798.5V128.5h-53.914v-19.662h17.015V27.35h-17.015V7.5H798.5ZM256.444 44.146h-20.071V27.35h-13.959V128.5h-20.071V27.35h-13.958v16.796H168.5V24.98l.034-16.982V7.5h87.683l.007.493.22 16.984v19.169Z" />
</g>
</svg>
);
}
function FooterFrame({ children }: { children: ReactNode }) {
return (
<div className="relative isolate mx-auto w-full px-2 lg:w-[calc(100%-4rem)] lg:border-primary-foreground/15 lg:border-x lg:border-dashed">
<Grain className="hidden lg:block" />
<GridMarker className="hidden lg:block" position="top-left" />
<GridMarker className="hidden lg:block" position="top-right" />
<GridMarker className="hidden lg:block" position="bottom-left" />
<GridMarker className="hidden lg:block" position="bottom-right" />
<div className="relative mx-auto w-full max-w-6xl border-primary-foreground/15 border-x border-dashed">
{children}
<GridMarker position="top-left" />
<GridMarker position="top-right" />
<GridMarker position="bottom-left" />
<GridMarker position="bottom-right" />
</div>
</div>
);
}
export function Footer02({
brandName = "TentUI",
brandHref = "#",
brandMark,
copyright = "© 2026",
linkGroups = DEFAULT_LINK_GROUPS,
socialLinks = DEFAULT_SOCIAL_LINKS,
wordmark,
className,
}: Footer02Props) {
return (
<footer
className={cn(
"relative isolate w-full overflow-hidden bg-primary text-primary-foreground",
className,
)}
>
<Grain />
<FooterFrame>
<div className="flex flex-col divide-y divide-dashed divide-primary-foreground/15 md:flex-row md:divide-x md:divide-y-0">
<div className="px-4 py-7 md:flex-1">
<div className="flex items-center gap-2">
<Link
aria-label={`${brandName} home`}
className="flex w-fit items-center gap-2"
href={brandHref as Route}
>
{brandMark ?? <TentMark />}
<span className="font-mono text-[12px] text-white tracking-tight">
{brandName}
</span>
</Link>
<span aria-hidden="true" className="text-primary-foreground/35">
·
</span>
<span className="font-mono text-[12px] text-primary-foreground/65 tracking-tight">
{copyright}
</span>
</div>
<FooterLinkList links={socialLinks} />
</div>
{linkGroups.map((group) => (
<nav
aria-label={group.title}
className="relative px-4 py-7 md:flex-1"
key={group.title}
>
<GridMarker position="top-left" />
<h2 className="font-mono text-[12px] tracking-tight">
{group.title}
</h2>
<FooterLinkList links={group.links} />
</nav>
))}
</div>
{wordmark ?? <TentWordmark />}
</FooterFrame>
</footer>
);
}
A grain-textured footer with dashed grid lines, navigation columns, and an oversized wordmark
@tentui/footer-02
Files
"use client";
import { ArrowRightIcon, MoonIcon, SunIcon } from "lucide-react";
import type { Route } from "next";
import Image from "next/image";
import Link from "next/link";
import { useTheme } from "next-themes";
import { type FormEvent, type ReactNode, useId } from "react";
import { Button } from "@/components/ui/button";
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/components/ui/input-group";
import { Separator } from "@/components/ui/separator";
import { cn } from "@/lib/utils";
export interface Footer01Link {
label: string;
href: string;
}
export interface Footer01LinkGroup {
title: string;
links: Footer01Link[];
}
export interface Footer01Props {
logo?: ReactNode;
brandName?: string;
brandHref?: string;
headline?: string;
description?: string;
inputPlaceholder?: string;
buttonText?: string;
copyright?: string;
linkGroups?: Footer01LinkGroup[];
legalLinks?: Footer01Link[];
newsletterAction?: string;
}
const DEFAULT_LINK_GROUPS: Footer01LinkGroup[] = [
{
title: "Product",
links: [
{ label: "Overview", href: "#" },
{ label: "Pricing", href: "#" },
{ label: "Dashboard", href: "#" },
],
},
{
title: "Resources",
links: [
{ label: "Blog", href: "#" },
{ label: "Changelog", href: "#" },
],
},
{
title: "Legal",
links: [
{ label: "Privacy", href: "#" },
{ label: "Terms", href: "#" },
{ label: "Fair use", href: "#" },
],
},
];
const DEFAULT_LEGAL_LINKS =
DEFAULT_LINK_GROUPS.find((group) => group.title === "Legal")?.links ?? [];
function GridTick({ className }: { className?: string }) {
return (
<span
aria-hidden="true"
className={cn(
"pointer-events-none absolute z-10 size-4 text-border",
"before:absolute before:top-1/2 before:left-0 before:h-px before:w-full before:-translate-y-1/2 before:bg-current",
"after:absolute after:top-0 after:left-1/2 after:h-full after:w-px after:-translate-x-1/2 after:bg-current",
className,
)}
/>
);
}
function FooterLink({
link,
className,
}: {
link: Footer01Link;
className?: string;
}) {
const external = link.href.startsWith("http");
return (
<Link
className={className}
href={link.href as Route}
rel={external ? "noreferrer" : undefined}
target={external ? "_blank" : undefined}
>
{link.label}
</Link>
);
}
function FooterLinkList({ links }: { links: Footer01Link[] }) {
return (
<ul className="flex flex-col gap-3.5">
{links.map((link) => (
<li key={`${link.label}-${link.href}`}>
<FooterLink
className="block py-0.5 font-medium text-muted-foreground text-sm transition-colors duration-150 ease-[cubic-bezier(0.23,1,0.32,1)] hover:text-foreground"
link={link}
/>
</li>
))}
</ul>
);
}
function FooterLinkColumn({ group }: { group: Footer01LinkGroup }) {
return (
<nav aria-label={group.title} className="flex flex-col gap-4 pl-6">
<p className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
{group.title}
</p>
<FooterLinkList links={group.links} />
</nav>
);
}
function NewsletterForm({
placeholder,
buttonText,
action,
}: {
placeholder: string;
buttonText: string;
action?: string;
}) {
const inputId = useId();
function handleSubmit(event: FormEvent<HTMLFormElement>) {
if (!action) event.preventDefault();
}
return (
<form
action={action}
className="w-full max-w-md"
method={action ? "post" : undefined}
onSubmit={handleSubmit}
>
<label className="sr-only" htmlFor={inputId}>
Email address
</label>
<InputGroup className="h-12">
<InputGroupInput
autoComplete="email"
className="h-full px-4"
id={inputId}
name="email"
placeholder={placeholder}
required
spellCheck={false}
type="email"
/>
<InputGroupAddon align="inline-end">
<InputGroupButton
className="min-w-28 touch-manipulation transition-transform duration-160 ease-[cubic-bezier(0.23,1,0.32,1)] active:translate-y-0 active:scale-[0.98]"
size="sm"
type="submit"
variant="default"
>
{buttonText}
<ArrowRightIcon aria-hidden="true" data-icon="inline-end" />
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</form>
);
}
function ThemeToggle() {
const { resolvedTheme, setTheme } = useTheme();
return (
<Button
aria-label="Toggle theme"
className="relative touch-manipulation transition-transform duration-160 ease-[cubic-bezier(0.23,1,0.32,1)] active:scale-[0.97]"
onClick={() => setTheme(resolvedTheme === "dark" ? "light" : "dark")}
size="icon-sm"
type="button"
variant="ghost"
>
<SunIcon
aria-hidden="true"
className="dark:hidden"
data-icon="inline-start"
/>
<MoonIcon
aria-hidden="true"
className="hidden dark:block"
data-icon="inline-start"
/>
</Button>
);
}
function Wordmark({
logo,
brandName,
brandHref,
}: {
logo?: ReactNode;
brandName: string;
brandHref: string;
}) {
return (
<Link
aria-label={`${brandName} home`}
className="inline-flex w-fit items-center"
href={brandHref as Route}
>
{logo ?? (
<Image
alt=""
aria-hidden="true"
className="h-9 w-auto"
height={166}
src="https://cdn.srb.codes/tkit-logo.svg"
unoptimized
width={455}
/>
)}
</Link>
);
}
export function Footer01({
logo,
brandName = "Tkit.ai",
brandHref = "#",
headline = "The toolkit for turning your website visitors into clients.",
description = "Support tickets, infrastructure, and workflows to ship features fast.",
inputPlaceholder = "Enter your email…",
buttonText = "Subscribe",
copyright = `© ${new Date().getUTCFullYear()} All rights reserved.`,
linkGroups = DEFAULT_LINK_GROUPS,
legalLinks = DEFAULT_LEGAL_LINKS,
newsletterAction,
}: Footer01Props) {
const [primaryGroup, ...secondaryGroups] = linkGroups;
return (
<footer className="relative w-full overflow-hidden p-2 font-sans">
<div className="relative mx-auto w-full max-w-6xl border bg-background text-foreground">
<GridTick className="-top-2 -left-2" />
<GridTick className="-top-2 -right-2" />
<GridTick className="-bottom-2 -left-2" />
<GridTick className="-right-2 -bottom-2" />
<div className="grid grid-cols-1 overflow-hidden lg:grid-cols-14">
<div className="relative border-b lg:col-span-8 lg:border-r lg:border-b-0">
<div className="grid h-full grid-cols-1 md:grid-cols-[minmax(0,1fr)_12rem]">
<div className="flex min-h-[300px] min-w-0 flex-col justify-center gap-7 p-5 sm:min-h-[340px] sm:p-8 lg:p-12">
<div className="flex flex-col gap-4">
<h2 className="max-w-lg text-balance font-light text-2xl leading-[1.15] tracking-tight sm:text-3xl lg:text-4xl">
{headline}
</h2>
<p className="max-w-md text-muted-foreground text-sm leading-relaxed">
{description}
</p>
</div>
<NewsletterForm
action={newsletterAction}
buttonText={buttonText}
placeholder={inputPlaceholder}
/>
</div>
{primaryGroup ? (
<div className="border-t px-5 py-8 sm:px-8 md:border-t-0 md:border-l md:px-6 md:py-12">
<FooterLinkColumn group={primaryGroup} />
</div>
) : null}
</div>
<GridTick className="-top-2 -right-2 hidden lg:block" />
<GridTick className="-right-2 -bottom-2 hidden lg:block" />
</div>
<div className="relative px-5 py-8 sm:px-8 sm:py-12 lg:col-span-6">
<div className="grid h-full grid-cols-2 gap-7 sm:gap-4">
{secondaryGroups.map((group) => (
<FooterLinkColumn group={group} key={group.title} />
))}
</div>
</div>
</div>
<div className="flex flex-col gap-7 border-t px-5 py-7 sm:px-8 sm:py-8 lg:flex-row lg:items-center lg:justify-between lg:gap-8 lg:px-12 lg:py-10">
<Wordmark brandHref={brandHref} brandName={brandName} logo={logo} />
<div className="flex flex-1 flex-col gap-5 md:flex-row md:items-center md:gap-8 lg:justify-end">
<div className="flex flex-col gap-4 sm:flex-row sm:items-center">
<p className="max-w-[220px] shrink-0 text-muted-foreground text-xs">
{copyright}
</p>
{legalLinks.length > 0 ? (
<>
<Separator
className="hidden h-4 sm:block"
orientation="vertical"
/>
<nav aria-label="Legal">
<ul className="flex flex-wrap items-center gap-4">
{legalLinks.map((link) => (
<li key={`${link.label}-${link.href}`}>
<FooterLink
className="font-medium text-muted-foreground text-xs transition-colors duration-150 ease-[cubic-bezier(0.23,1,0.32,1)] hover:text-foreground"
link={link}
/>
</li>
))}
</ul>
</nav>
</>
) : null}
</div>
<ThemeToggle />
</div>
</div>
</div>
</footer>
);
}
A structured product footer with newsletter, navigation, status, and theme controls
@tentui/footer-01