Shadcn blocks for websites
Copy complete React sections into your project, then customize the source to match your product. Need an end-to-end starting point? Build a shadcn landing page with these blocks.
Files
"use client";
import { ArrowUpRight, Check } from "lucide-react";
import FluidWave from "./fluid-wave";
type Plan = {
name: string;
description: string;
price: number;
features: readonly string[];
cta: string;
href: string;
popular?: boolean;
};
const PLANS: readonly Plan[] = [
{
name: "Core",
description: "For individuals turning focused ideas into real products.",
price: 12,
features: [
"3 active projects",
"Unlimited collaborators",
"5 GB file storage",
"Community support",
],
cta: "Start with Core",
href: "/signup?plan=core",
},
{
name: "Studio",
description: "For ambitious teams shipping together, without the busywork.",
price: 29,
features: [
"Unlimited projects",
"Advanced permissions",
"100 GB file storage",
"Priority support",
],
cta: "Choose Studio",
href: "/signup?plan=studio",
popular: true,
},
];
function PlanCard({ plan }: { plan: Plan }) {
return (
<article
aria-label={`${plan.name} plan`}
className={`relative isolate flex min-h-[34rem] overflow-hidden rounded-[2rem] border p-7 sm:p-9 ${
plan.popular
? "border-white/10 bg-[#0a0a0a] text-white shadow-[0_24px_70px_-32px_rgba(252,76,1,0.42)]"
: "border-border bg-card text-card-foreground shadow-[0_24px_70px_-42px_rgba(15,23,42,0.3)]"
}`}
>
{plan.popular ? (
<>
<div
aria-hidden="true"
className="absolute inset-0 -z-20 bg-[radial-gradient(circle_at_70%_100%,rgba(252,76,1,0.66),transparent_66%)]"
/>
<div aria-hidden="true" className="absolute inset-0 -z-10">
<FluidWave />
</div>
<div
aria-hidden="true"
className="absolute inset-0 -z-10 bg-[linear-gradient(to_bottom,#0a0a0a_8%,rgba(10,10,10,0.9)_38%,rgba(10,10,10,0.36)_72%,rgba(10,10,10,0.08))]"
/>
</>
) : null}
<div className="flex w-full flex-col">
<div className="flex items-start justify-between gap-4">
<div>
<h3 className="font-medium text-2xl tracking-[-0.03em]">
{plan.name}
</h3>
<p
className={`mt-3 max-w-sm text-sm leading-6 ${
plan.popular ? "text-white/62" : "text-muted-foreground"
}`}
>
{plan.description}
</p>
</div>
{plan.popular ? (
<span className="shrink-0 whitespace-nowrap rounded-full border border-white/15 bg-white/10 px-3 py-1 font-medium text-[0.68rem] text-white/90 uppercase tracking-[0.14em] backdrop-blur-md">
Most popular
</span>
) : null}
</div>
<div className="mt-10 flex items-end gap-2">
<span className="font-medium text-5xl tracking-[-0.055em]">
${plan.price}
</span>
<span
className={`pb-1 text-sm ${
plan.popular ? "text-white/58" : "text-muted-foreground"
}`}
>
per month
</span>
</div>
<div
className={`my-8 h-px ${plan.popular ? "bg-white/12" : "bg-border"}`}
/>
<ul className="space-y-4">
{plan.features.map((feature) => (
<li className="flex items-center gap-3 text-sm" key={feature}>
<span
className={`flex size-5 shrink-0 items-center justify-center rounded-full ${
plan.popular ? "bg-white/14" : "bg-muted"
}`}
>
<Check
aria-hidden="true"
className="size-3"
strokeWidth={2.5}
/>
</span>
<span className={plan.popular ? "text-white/82" : undefined}>
{feature}
</span>
</li>
))}
</ul>
<a
className={`group mt-auto inline-flex h-12 touch-manipulation items-center justify-between rounded-full px-5 font-medium text-sm outline-none transition-[transform,background-color,color] duration-150 ease-[cubic-bezier(0.23,1,0.32,1)] focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 active:scale-[0.97] motion-reduce:transition-none ${
plan.popular
? "bg-white text-[#111217] [@media(hover:hover)_and_(pointer:fine)]:hover:bg-white/88"
: "bg-foreground text-background [@media(hover:hover)_and_(pointer:fine)]:hover:bg-foreground/86"
}`}
href={plan.href}
>
{plan.cta}
<span className="flex size-7 items-center justify-center rounded-full bg-current/10">
<ArrowUpRight aria-hidden="true" className="size-3.5" />
</span>
</a>
</div>
</article>
);
}
export function Pricing03() {
return (
<section className="relative overflow-hidden bg-background px-4 py-20 text-foreground sm:px-6 sm:py-28 lg:px-8">
<div
aria-hidden="true"
className="absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-border to-transparent"
/>
<div className="mx-auto max-w-5xl">
<div className="mb-12 grid gap-7 md:mb-16 md:grid-cols-[1fr_0.72fr] md:items-end">
<h2 className="max-w-xl text-balance font-medium text-4xl tracking-[-0.045em] sm:text-5xl sm:leading-[1.02]">
Plans that scale with the work.
</h2>
<p className="max-w-md text-pretty text-muted-foreground text-sm leading-6 md:justify-self-end">
Start small, then move up when your team is ready. Every plan
includes a 14-day trial and no long-term contract.
</p>
</div>
<div className="grid gap-5 lg:grid-cols-2">
{PLANS.map((plan) => (
<PlanCard key={plan.name} plan={plan} />
))}
</div>
<p className="mt-7 text-center text-muted-foreground text-xs">
Prices are in USD. Cancel or change your plan at any time.
</p>
</div>
</section>
);
}
A focused two-plan pricing section with an ambient fluid treatment for the popular plan
@tentui/pricing-03
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
"use client";
import { PlusIcon } from "lucide-react";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Bubble, BubbleContent } from "@/components/ui/bubble";
import { Button } from "@/components/ui/button";
import {
Message,
MessageAvatar,
MessageContent,
} from "@/components/ui/message";
import { Separator } from "@/components/ui/separator";
const FAQS = [
{
id: "getting-started",
question: "How quickly can I get started?",
answer:
"You can be up and running in a few minutes. Install the package, choose the components you need, and customize them directly in your codebase.",
},
{
id: "customize",
question: "Can I customize every component?",
answer:
"Yes. You own the source code, so every detail is yours to adapt — from tokens and typography to layout, behavior, and motion.",
},
{
id: "frameworks",
question: "Which frameworks are supported?",
answer:
"The components are designed for React and work especially well with Next.js. They use standard TypeScript, Tailwind CSS, and accessible primitives.",
},
{
id: "accessibility",
question: "Are the components accessible?",
answer:
"Accessibility is built into the underlying primitives, including keyboard navigation, focus management, and the appropriate ARIA attributes.",
},
{
id: "updates",
question: "Do I get future updates?",
answer:
"Yes. You can pull newer versions whenever they are useful while keeping full control over the local changes you have made.",
},
];
export function Faq03() {
return (
<section
aria-labelledby="faq-03-heading"
className="w-full bg-background py-20 font-sans sm:py-28"
>
<header className="mx-auto flex max-w-4xl flex-col items-center gap-4 text-center">
<h2
className="text-balance font-normal text-4xl text-foreground leading-[1.08] tracking-[-0.03em] sm:text-5xl"
id="faq-03-heading"
>
Questions, answered
</h2>
<p className="max-w-xl text-balance text-base text-muted-foreground leading-relaxed sm:text-lg">
Everything you need to know before adding the library to your next
project.
</p>
</header>
<div className="relative mx-auto mt-14 max-w-4xl px-1 py-6 sm:mt-16 sm:px-6 sm:py-8">
<Separator
aria-hidden="true"
className="pointer-events-none absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-border to-transparent"
/>
<Separator
aria-hidden="true"
className="pointer-events-none absolute inset-x-0 bottom-0 h-px bg-gradient-to-r from-transparent via-border to-transparent"
/>
<Accordion className="gap-3" defaultValue={["getting-started"]}>
{FAQS.map((faq) => (
<AccordionItem
className="border-0 not-last:border-b-0"
key={faq.id}
value={faq.id}
>
<AccordionTrigger className="group/question [&>[data-slot=accordion-trigger-icon]]:hidden! ml-auto w-fit max-w-[92%] flex-none touch-manipulation items-center justify-end gap-2 rounded-full border-0 py-0 font-normal transition-none hover:no-underline focus-visible:ring-2 sm:max-w-[82%]">
<span className="flex size-9 shrink-0 items-center justify-center rounded-full border border-border bg-background text-muted-foreground shadow-xs transition-[transform,background-color,border-color,color] duration-160 ease-[cubic-bezier(0.23,1,0.32,1)] group-focus-visible/question:scale-100 group-focus-visible/question:transition-none group-active/question:scale-[0.96] group-aria-expanded/question:border-primary group-aria-expanded/question:bg-primary group-aria-expanded/question:text-primary-foreground motion-reduce:transition-colors motion-reduce:group-active/question:scale-100">
<PlusIcon
aria-hidden="true"
className="size-4 transition-transform duration-180 ease-[cubic-bezier(0.77,0,0.175,1)] group-focus-visible/question:transition-none group-aria-expanded/question:rotate-45 motion-reduce:transition-none"
/>
</span>
<span className="rounded-full border border-border bg-background px-5 py-3 text-left font-medium text-base leading-relaxed shadow-xs transition-[transform,background-color,border-color,color] duration-160 ease-[cubic-bezier(0.23,1,0.32,1)] group-focus-visible/question:scale-100 group-focus-visible/question:transition-none group-active/question:scale-[0.98] group-aria-expanded/question:border-primary group-aria-expanded/question:bg-primary group-aria-expanded/question:text-primary-foreground motion-reduce:transition-colors motion-reduce:group-active/question:scale-100">
{faq.question}
</span>
</AccordionTrigger>
<AccordionContent className="pt-3 pb-5 sm:pb-6">
<Message align="start">
<MessageAvatar>
<Avatar size="lg">
<AvatarFallback>TS</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<Bubble
className="max-w-[92%] sm:max-w-[80%]"
variant="muted"
>
<BubbleContent className="rounded-2xl rounded-bl-sm px-5 py-4 text-base leading-relaxed">
{faq.answer}
</BubbleContent>
</Bubble>
</MessageContent>
</Message>
</AccordionContent>
</AccordionItem>
))}
</Accordion>
</div>
<div className="mx-auto mt-6 flex max-w-4xl flex-col items-center justify-between gap-4 px-1 sm:flex-row sm:px-6">
<p className="text-center text-base text-muted-foreground sm:text-left">
Still have a question?
</p>
<Button
nativeButton={false}
render={<a href="mailto:hello@example.com" />}
size="lg"
>
Send us a message
</Button>
</div>
</section>
);
}
A conversational FAQ section with chat bubbles and a compact contact prompt
@tentui/faq-03
Files
"use client";
import { ArrowRight, Check } from "lucide-react";
import { useInView, useMotionValueEvent, useSpring } from "motion/react";
import { useEffect, useRef, useState } from "react";
import { DashboardIcon, MousePointerClick01Icon, RocketIcon } from "./icons";
export type PricingPlan = {
title: string;
description: string;
price: number;
period: string;
features: string[];
cta: string;
ctaLink: string;
isHorizontal?: boolean;
showCustomBadge?: boolean;
showStartsAt?: boolean;
icon?: React.ReactNode;
};
// ── Tab categories with their plans ──────────────────────────────────
type TabCategory = {
label: string;
value: string;
plans: PricingPlan[];
};
const tabCategories: TabCategory[] = [
{
label: "Landing",
value: "landing",
plans: [
{
title: "Landing Page",
icon: (
<MousePointerClick01Icon
intervalDuration={2000}
className="size-6 text-zinc-900 dark:text-zinc-100"
/>
),
description: "Perfect to build your brand and get leads.",
price: 1995,
period: "one time",
features: [
"Design + Development",
"Search Engine Optimization",
"Responsive Design",
"4-7 days turnaround time",
"90+ Web vitals performance",
],
cta: "Book a Call",
ctaLink: "#",
},
],
},
{
label: "Full Site",
value: "fullsite",
plans: [
{
title: "Multi Page Website",
icon: (
<DashboardIcon
intervalDuration={2000}
className="size-6 text-zinc-900 dark:text-zinc-100"
/>
),
description: "Best for startups and businesses.",
price: 2995,
period: "/mo",
features: [
"Design + Development",
"Search Engine Optimization",
"Responsive Design",
"+$300 per additional page",
"7-10 days turnaround time",
],
cta: "Book a Call",
ctaLink: "#",
},
],
},
{
label: "Product",
value: "product",
plans: [
{
title: "MVP Development",
icon: (
<RocketIcon
intervalDuration={2000}
className="size-6 text-zinc-900 dark:text-zinc-100"
/>
),
description: "Ideal for quick product validation",
price: 2995,
period: "one time",
features: [
"Design + Development + Deployment",
"Core Feature Implementation",
"Responsive Design",
"Idea to Production",
"10-15 days turnaround time",
],
cta: "Book a Call",
ctaLink: "#",
},
],
},
];
// ── Static "Most Popular" plan (always visible) ──────────────────────
const mostPopularPlan: PricingPlan = {
title: "Design/Dev Retainer",
description:
"A dedicated engineer for a fraction of the cost of a full-time hire.",
price: 2995,
period: "/mo",
features: [
"Unlimited design & dev requests",
"Average 48-hr turnaround",
"Dedicated async channel",
"Pause or cancel anytime",
],
cta: "Get Started",
ctaLink: "#",
isHorizontal: true,
showCustomBadge: true,
};
function formatPrice(price: number) {
return price.toLocaleString("en-US", {
style: "currency",
currency: "USD",
minimumFractionDigits: 0,
});
}
const PRICE_SPRING = {
stiffness: 80,
damping: 14,
mass: 0.5,
};
export function PricingCard({ plan }: { plan: PricingPlan }) {
const priceRef = useRef<HTMLDivElement>(null);
const isInView = useInView(priceRef, { once: true });
const priceSpring = useSpring(0, PRICE_SPRING);
const [springyPrice, setSpringyPrice] = useState(0);
useMotionValueEvent(priceSpring, "change", (latest) => {
setSpringyPrice(Number(latest.toFixed(0)));
});
useEffect(() => {
if (isInView) {
priceSpring.set(plan.price ?? 0);
}
}, [isInView, plan.price, priceSpring]);
if (plan.isHorizontal) {
return (
<div className="relative w-full md:col-span-2">
<style>{`
.agency-pricing-badge::before {
content: 'Most Popular';
position: absolute;
width: 150%;
height: 40px;
background-image: linear-gradient(45deg, #ff6547 0%, #ffb144 51%, #ff7053 100%);
transform: rotate(-45deg) translateY(-20px);
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-weight: 600;
letter-spacing: 0.1em;
text-transform: uppercase;
box-shadow: 0 5px 10px rgba(0,0,0,0.23);
}
.agency-pricing-badge::after {
content: '';
position: absolute;
width: 10px;
bottom: 0;
left: 0;
height: 10px;
z-index: -1;
box-shadow: 140px -140px #cc3f47;
background-image: linear-gradient(45deg, #FF512F 0%, #F09819 51%, #FF512F 100%);
}
`}</style>
{/* Custom Badge */}
{plan.showCustomBadge && (
<span className="agency-pricing-badge absolute -top-2.5 -left-2.5 z-20 flex h-[150px] w-[150px] items-center justify-center overflow-hidden" />
)}
<div className="overflow-hidden rounded-[23px] border p-1">
<div className="grid gap-6 rounded-[22px] bg-gradient-to-b from-zinc-50 to-zinc-100 p-4 md:grid-cols-[1fr_auto] dark:from-zinc-900 dark:to-zinc-900">
<div className="flex flex-col">
<div className="mb-10 flex flex-col">
<div
className={`flex items-start justify-between gap-2 ${plan.showCustomBadge ? "pt-9 pl-9" : ""}`}
>
<div className="flex flex-col gap-2">
{plan.icon && <div>{plan.icon}</div>}
<h2 className="mb-2 font-medium text-[24px]">
{plan.title}
</h2>
</div>
</div>
<p className="max-w-md text-muted-foreground text-sm">
{plan.description}
</p>
</div>
<div ref={priceRef} className="mb-6">
<p className="mb-1 text-muted-foreground text-sm">starts at</p>
<div className="flex items-baseline gap-2">
<span className="font-medium text-4xl">
{formatPrice(springyPrice)}
</span>
{plan.period && (
<span className="text-muted-foreground text-sm">
{plan.period}
</span>
)}
</div>
</div>
<ul className="mb-10 grid grid-cols-1 gap-3 md:grid-cols-2">
{plan.features.map((feature, featureIndex) => (
<li
key={featureIndex}
className="flex w-full items-start gap-3 text-sm"
>
<span className="mt-1.5 size-1.5 shrink-0 rounded-full bg-foreground" />
<span>{feature}</span>
</li>
))}
</ul>
<CardButton
ctaLink={plan.ctaLink}
cta={plan.cta}
className="w-full"
/>
</div>
</div>
</div>
</div>
);
}
return (
<div className="rounded-[23px] border border-border p-1">
{/* Header with Icon and Title */}
<div className="rounded-[22px] bg-gradient-to-b from-zinc-50 to-zinc-100 p-3 dark:from-zinc-900 dark:to-zinc-900">
<div className="mb-2 flex flex-col gap-2">
{plan.icon && <div className="mb-1">{plan.icon}</div>}
<h2 className="mb-1 font-medium text-lg">{plan.title}</h2>
</div>
{/* Description Box */}
<div className="mb-4 rounded-lg bg-muted p-2 text-muted-foreground text-xs">
{plan.description}
</div>
{/* Features List */}
<ul className="mb-4 space-y-2">
{plan.features.map((feature, featureIndex) => (
<li key={featureIndex} className="flex items-start gap-2 text-xs">
<Check className="mt-0.5 size-3.5 shrink-0 text-foreground" />
<span className="min-w-0 text-foreground">{feature}</span>
</li>
))}
</ul>
</div>
{/* Footer Buttons */}
<div className="flex flex-col gap-3 p-3">
{/* Pricing */}
<div
ref={priceRef}
className="flex flex-wrap items-baseline gap-1.5 px-1"
>
<span className="font-bold text-2xl text-foreground">
{formatPrice(springyPrice)}
</span>
{plan.period && (
<span className="text-muted-foreground text-xs">{plan.period}</span>
)}
</div>
<div className="flex flex-col gap-2 px-1">
<CardButton
ctaLink={plan.ctaLink}
cta={plan.cta}
className="w-full"
/>
</div>
</div>
</div>
);
}
function CardButton({
ctaLink,
cta,
className,
}: {
ctaLink: string;
cta: string;
className?: string;
}) {
function handleClick() {
if (ctaLink === "#") return;
if (ctaLink.startsWith("http")) {
window.open(ctaLink, "_blank", "noopener,noreferrer");
return;
}
window.location.assign(ctaLink);
}
return (
<button
type="button"
onClick={handleClick}
className={`group/agency-cta inline-flex h-10 shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-lg border border-foreground/10 bg-foreground bg-clip-padding px-4 font-medium text-background text-sm shadow-[inset_0_1px_0_rgb(255_255_255_/_0.16),0_1px_2px_rgb(0_0_0_/_0.2)] outline-none transition-[transform,background-color,box-shadow] duration-150 ease-[cubic-bezier(0.23,1,0.32,1)] hover:bg-foreground/90 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:scale-[0.97] motion-reduce:transition-none ${className ?? ""}`}
>
<span className="text-sm">{cta}</span>
<span className="block h-4 w-px bg-background/25" />
<span
aria-hidden="true"
className="size-6 overflow-hidden rounded-full bg-background/10"
>
<span className="flex w-12 -translate-x-1/2 transition-transform duration-200 ease-[cubic-bezier(0.23,1,0.32,1)] group-hover/agency-cta:translate-x-0 motion-reduce:transform-none motion-reduce:transition-none">
<span className="flex size-6 shrink-0">
<ArrowRight className="m-auto size-3" />
</span>
<span className="flex size-6 shrink-0">
<ArrowRight className="m-auto size-3" />
</span>
</span>
</span>
</button>
);
}
// ── Component ────────────────────────────────────────────────────────
export function Pricing02() {
const [activeTab, setActiveTab] = useState("landing");
const activeCategory =
tabCategories.find((category) => category.value === activeTab) ??
tabCategories[0];
return (
<div
id="pricing"
className="mx-auto max-w-4xl space-y-6 border border-border px-4 py-12 md:space-y-8 md:py-16"
>
{/* Section Header */}
<div className="mb-8 text-center md:mb-16">
<h1 className="mb-3 font-medium text-2xl md:mb-4 md:text-4xl">
No Contract, No Surprises
</h1>
<p className="mx-auto max-w-2xl px-2 text-muted-foreground text-sm md:px-0 md:text-base">
Consistent Pricing and Value Each Month, with the Flexibility to
Cancel Anytime
</p>
</div>
{/* Two-column layout: tabs + plans (left) | static popular (right) */}
<div className="grid grid-cols-1 gap-6 border-border border-y py-4 md:grid-cols-2 md:gap-8 md:py-6">
{/* ── Left: Tab Navigation + Dynamic Plans ── */}
<div className="flex flex-col gap-4">
<div
role="group"
aria-label="Project type"
className="inline-flex h-9 w-full items-center justify-center rounded-lg bg-muted p-[3px] text-muted-foreground"
>
{tabCategories.map((category) => {
const isActive = category.value === activeTab;
return (
<button
key={category.value}
type="button"
aria-pressed={isActive}
onClick={() => setActiveTab(category.value)}
className={`inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center whitespace-nowrap rounded-md border px-2 py-1 font-medium text-sm outline-none transition-[color,background-color,border-color,box-shadow,transform] duration-150 ease-[cubic-bezier(0.23,1,0.32,1)] focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:scale-[0.97] motion-reduce:transition-none ${
isActive
? "border-transparent bg-background text-foreground shadow-sm dark:border-input dark:bg-input/30"
: "border-transparent text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground"
}`}
>
{category.label}
</button>
);
})}
</div>
<div className="mt-2 flex flex-col gap-4">
{activeCategory.plans.map((plan) => (
<PricingCard key={plan.title} plan={plan} />
))}
</div>
</div>
{/* ── Right: Static "Most Popular" Plan ── */}
<div className="flex flex-col">
<PricingCard plan={mostPopularPlan} />
</div>
</div>
</div>
);
}
Tabbed project pricing paired with a highlighted ongoing retainer
@tentui/pricing-02
Files
"use client";
import { MotionConfig, motion } from "motion/react";
import Image from "next/image";
import { useRouter } from "next/navigation";
import { PeepingButton } from "@/components/peeping-button";
import { Button } from "@/components/ui/button";
import { SiteHeader } from "./header";
export { SiteHeader };
const EASE_OUT = [0.23, 1, 0.32, 1] as const;
export const HERO_02_ASSETS = {
background: "https://cdn.srb.codes/pixel-mountain-lake-hero-v2.png",
dashboard: "https://cdn.srb.codes/saas-hero-dashboard.png",
} as const;
const SERIF =
"'Iowan Old Style', 'Palatino Linotype', Georgia, Cambria, 'Times New Roman', serif";
export type Hero02Props = {
backgroundImageSrc?: string;
dashboardImageSrc?: string;
};
export function Hero02({
backgroundImageSrc = HERO_02_ASSETS.background,
dashboardImageSrc = HERO_02_ASSETS.dashboard,
}: Hero02Props = {}) {
const router = useRouter();
return (
<MotionConfig reducedMotion="user">
<SiteHeader />
<section className="relative w-full text-foreground">
{/* Hero body */}
<div className="mx-auto flex max-w-4xl flex-col items-center px-6 pt-16 text-center sm:pt-24">
<motion.h1
animate={{ opacity: 1, transform: "translateY(0px)" }}
className="mt-8 font-normal text-[3.5rem] text-foreground leading-[1.04] tracking-[-0.02em] md:text-[5.25rem]"
initial={{ opacity: 0, transform: "translateY(22px)" }}
style={{ fontFamily: SERIF }}
transition={{ duration: 0.55, delay: 0.08, ease: EASE_OUT }}
>
Turn website visitors
<br className="hidden sm:block" /> into{" "}
<em className="italic">Customers</em>
</motion.h1>
<motion.p
animate={{ opacity: 1, transform: "translateY(0px)" }}
className="mt-7 max-w-lg text-balance text-[1.05rem] text-muted-foreground leading-relaxed sm:text-lg"
initial={{ opacity: 0, transform: "translateY(16px)" }}
transition={{ duration: 0.5, delay: 0.2, ease: EASE_OUT }}
>
Customer support, sales & automations in five minutes.
</motion.p>
<motion.div
animate={{ opacity: 1, transform: "translateY(0px)" }}
className="mt-9 flex flex-col items-center gap-3 sm:flex-row"
initial={{ opacity: 0, transform: "translateY(16px)" }}
transition={{ duration: 0.5, delay: 0.3, ease: EASE_OUT }}
>
<Button
aria-label="Start free trial"
className="h-[51px] min-w-40"
onClick={() => router.push("#")}
size="lg"
>
Start free trial
</Button>
<PeepingButton
aria-label="Pricing"
className="min-h-[51px] min-w-[160px]"
coverClassName="px-6 py-4"
onClick={() => router.push("#")}
>
Pricing
</PeepingButton>
</motion.div>
<p className="mt-5 text-muted-foreground text-sm">
15-day free trial. No credit card required.
</p>
</div>
<div className="relative mx-auto mt-16 w-full max-w-[1600px] overflow-hidden bg-background sm:mt-24">
{/* Decorative product backdrop. */}
<Image
alt=""
aria-hidden="true"
className="object-cover object-center [image-rendering:pixelated]"
fill
priority
sizes="(min-width: 100rem) 100rem, 100vw"
src={backgroundImageSrc}
unoptimized
/>
{/* Product screenshot with an intrinsic aspect ratio, so it cannot collapse. */}
<div className="relative flex justify-center px-4 py-10 sm:px-10 sm:py-16 lg:px-16 lg:py-24">
<figure className="w-full max-w-6xl">
<div className="overflow-hidden rounded-lg border border-border/70 bg-background shadow-2xl ring-1 ring-background/50">
<Image
alt="Acme Tickets dashboard overview showing ticket sources and usage"
className="h-auto w-full"
height={1684}
priority
sizes="(min-width: 80rem) 72rem, (min-width: 40rem) calc(100vw - 5rem), calc(100vw - 2rem)"
src={dashboardImageSrc}
unoptimized
width={2940}
/>
</div>
<figcaption className="mx-auto mt-4 w-fit rounded-full border border-border/70 bg-background/80 px-3 py-1 text-center text-foreground text-xs shadow-sm backdrop-blur-sm">
This dashboard uses demo data.
</figcaption>
</figure>
</div>
</div>
</section>
</MotionConfig>
);
}
An editorial hero with a playful pricing CTA and support dashboard preview
@tentui/hero-02
Files
import type { ReactNode } from "react";
import { getTweet, type Tweet } from "react-tweet/api";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { cn } from "@/lib/utils";
export interface TestimonialAuthor {
name: string;
handle: string;
avatarUrl?: string;
verified?: boolean;
}
export interface TestimonialData {
id: string;
text: string;
author: TestimonialAuthor;
}
export interface TestimonialSource {
id: string;
/** Controls this testimonial's placement in the bento grid. */
className?: string;
fallback?: Omit<TestimonialData, "id">;
}
export interface Testimonials01Props {
title?: ReactNode;
description?: ReactNode;
testimonials?: readonly TestimonialSource[];
className?: string;
gridClassName?: string;
}
const DEFAULT_TESTIMONIALS = [
{
id: "2048474453227540522",
className: "md:col-span-2",
fallback: {
text: "Very consistently looking design, well done :)",
author: {
name: "Jarek Avi",
handle: "JarekAvi",
avatarUrl:
"https://pbs.twimg.com/profile_images/2009380291572326400/K1X7xiJd_normal.jpg",
verified: true,
},
},
},
{
id: "2049878647591551410",
className: "md:col-span-1",
fallback: {
text: "cool",
author: {
name: "Gurbinder",
handle: "legionsdev",
avatarUrl:
"https://pbs.twimg.com/profile_images/1924504051728670720/mqyGd02m_normal.jpg",
verified: true,
},
},
},
{
id: "2037743266087756052",
className: "md:col-span-1",
fallback: {
text: "Looks nice!",
author: {
name: "Archit",
handle: "iarcI3",
avatarUrl:
"https://pbs.twimg.com/profile_images/2036806514640650240/YwsX6fu4_normal.jpg",
},
},
},
{
id: "2036810028838252769",
className: "md:col-span-2",
fallback: {
text: "Clean and fast exactly what we love",
author: {
name: "Rohit Girhe",
handle: "rohit_girhe",
avatarUrl:
"https://pbs.twimg.com/profile_images/2010004686737420294/OgjQdz4G_normal.jpg",
},
},
},
{
id: "2049014991836086285",
className: "md:col-span-2",
fallback: {
text: "Looks smooth mate 🔥",
author: {
name: "Nikolass | Video Editor",
handle: "NikolasDesignn",
avatarUrl:
"https://pbs.twimg.com/profile_images/1615137125573300224/pgKmbZh2_normal.jpg",
verified: true,
},
},
},
{
id: "2078686529443316059",
className: "md:col-span-2",
fallback: {
text: "Crazyyy components",
author: {
name: "vansh",
handle: "vanshdevx",
avatarUrl:
"https://pbs.twimg.com/profile_images/2043464015289151488/GFtfUf9M_normal.jpg",
verified: true,
},
},
},
{
id: "2081023285505007821",
className: "md:col-span-2",
fallback: {
text: "This is a badass library.",
author: {
name: "Coin Moebius",
handle: "coinmoebius",
avatarUrl:
"https://pbs.twimg.com/profile_images/2063997511509975040/LE-0tfyw_200x200.jpg",
},
},
},
] as const satisfies readonly TestimonialSource[];
function cleanText(text: string) {
return text.replace(/^(@\w+\s+)+/, "").trim();
}
function normalizeTweet(tweet: Tweet): TestimonialData {
const { user } = tweet;
return {
id: tweet.id_str,
text: cleanText(tweet.text),
author: {
name: user.name,
handle: user.screen_name,
avatarUrl: user.profile_image_url_https,
verified: Boolean(
user.verified || user.is_blue_verified || user.verified_type,
),
},
};
}
async function resolveTestimonial(source: TestimonialSource) {
const fallback = source.fallback
? { ...source.fallback, id: source.id }
: null;
try {
const tweet = await getTweet(source.id);
return tweet ? normalizeTweet(tweet) : fallback;
} catch {
return fallback;
}
}
function XLogo({ className }: { className?: string }) {
return (
<svg
aria-hidden="true"
className={className}
fill="currentColor"
viewBox="0 0 24 24"
>
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117L17.083 19.77z" />
</svg>
);
}
function VerifiedMark() {
return (
<svg
aria-label="Verified"
className="size-3.5 shrink-0 text-[#1d9bf0]"
fill="currentColor"
viewBox="0 0 22 22"
>
<path d="M20.396 11c-.018-.646-.215-1.275-.57-1.816-.354-.54-.852-.972-1.438-1.246.223-.607.27-1.264.14-1.897-.131-.634-.437-1.218-.882-1.687-.47-.445-1.053-.75-1.687-.882-.633-.13-1.29-.083-1.897.14-.273-.587-.704-1.086-1.245-1.44C12.275 1.819 11.647 1.62 11 1.604c-.646.017-1.275.213-1.815.568s-.972.854-1.246 1.44c-.608-.223-1.264-.27-1.898-.14-.633.13-1.217.437-1.687.882-.445.47-.751 1.053-.882 1.687-.13.633-.083 1.29.14 1.897-.586.274-1.086.705-1.44 1.246-.354.54-.55 1.17-.569 1.816.018.646.215 1.275.57 1.816.354.54.853.972 1.439 1.246-.223.607-.27 1.264-.14 1.897.131.634.437 1.218.882 1.687.47.445 1.053.751 1.687.882.633.131 1.29.083 1.897-.14.274.587.705 1.086 1.246 1.44.54.354 1.17.551 1.816.569.646-.018 1.275-.215 1.816-.57.54-.354.972-.852 1.246-1.439.607.223 1.264.27 1.897.14.634-.131 1.218-.437 1.687-.882.445-.47.751-1.053.882-1.687.13-.633.083-1.29-.14-1.897.587-.274 1.086-.705 1.44-1.246.354-.54.551-1.17.569-1.816zM9.662 14.85l-3.429-3.428 1.293-1.302 2.072 2.072 4.4-4.794 1.347 1.246z" />
</svg>
);
}
function initials(name: string) {
return name
.split(" ")
.map((part) => part[0])
.join("")
.slice(0, 2)
.toUpperCase();
}
function AuthorAvatar({ author }: { author: TestimonialAuthor }) {
const avatarUrl = author.avatarUrl?.replace("_normal.", "_bigger.");
return (
<Avatar className="size-9">
{avatarUrl ? <AvatarImage alt={author.name} src={avatarUrl} /> : null}
<AvatarFallback className="bg-foreground font-mono text-[11px] text-background">
{initials(author.name)}
</AvatarFallback>
</Avatar>
);
}
function Quote({ text }: { text: string }) {
const wordCount = text.split(/\s+/).length;
const isShort = wordCount <= 2;
const isLong = wordCount > 18;
const punctuated = /[.!?]$/.test(text) ? text : `${text}.`;
return (
<div
className="flex flex-1 items-center py-2"
data-slot="testimonial-quote-wrapper"
>
<blockquote
className={cn(
"text-pretty font-serif text-foreground tracking-tight",
isShort &&
"mx-auto text-center text-[clamp(3rem,12cqi,4.5rem)] italic leading-none",
!isShort &&
!isLong &&
"text-[clamp(1.0625rem,5cqi,3rem)] leading-[1.12]",
isLong && "text-[clamp(1.0625rem,3.5cqi,2rem)] leading-[1.2]",
)}
data-slot="testimonial-quote"
>
{isShort ? punctuated : <>“{text}”</>}
</blockquote>
</div>
);
}
function TestimonialCard({ testimonial }: { testimonial: TestimonialData }) {
const { author } = testimonial;
const url = `https://x.com/${author.handle}/status/${testimonial.id}`;
return (
<a
aria-label={`Read @${author.handle}'s post on X`}
className="group/card block h-full rounded-xl transition-transform duration-150 ease-[cubic-bezier(0.23,1,0.32,1)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/60 focus-visible:ring-offset-2 focus-visible:ring-offset-background active:scale-[0.995] motion-reduce:transition-none"
href={url}
rel="noreferrer"
target="_blank"
>
<Card className="@container/testimonial h-full gap-5 bg-card py-0">
<CardHeader className="flex flex-row items-start justify-between gap-3 px-5 pt-5 sm:px-6 sm:pt-6">
<div className="flex min-w-0 items-center gap-3">
<AuthorAvatar author={author} />
<div className="flex min-w-0 flex-col leading-tight">
<CardTitle className="flex items-center gap-1 font-medium font-sans text-sm leading-tight">
<span className="truncate">{author.name}</span>
{author.verified ? <VerifiedMark /> : null}
</CardTitle>
<CardDescription className="truncate font-mono text-[11px] leading-tight">
@{author.handle}
</CardDescription>
</div>
</div>
<XLogo className="size-4 shrink-0 text-muted-foreground/70 transition-colors duration-160 ease-[cubic-bezier(0.23,1,0.32,1)] [@media(hover:hover)_and_(pointer:fine)]:group-hover/card:text-foreground" />
</CardHeader>
<CardContent className="flex flex-1 flex-col px-5 pb-5 sm:px-6 sm:pb-6">
<Quote text={testimonial.text} />
</CardContent>
</Card>
</a>
);
}
export async function Testimonials01({
title = "Loved by thousands of people.",
description = "Here’s what some of our users have to say.",
testimonials = DEFAULT_TESTIMONIALS,
className,
gridClassName,
}: Testimonials01Props = {}) {
const resolved = await Promise.all(
testimonials.map(async (source) => ({
source,
testimonial: await resolveTestimonial(source),
})),
);
const available = resolved.filter(
(
item,
): item is {
source: TestimonialSource;
testimonial: TestimonialData;
} => item.testimonial !== null,
);
if (available.length === 0) {
return null;
}
return (
<section
className={cn(
"w-full bg-background px-2 py-20 font-sans sm:py-24 md:px-6 md:py-28",
className,
)}
>
<div className="mx-auto w-full max-w-6xl">
<header className="px-4 pb-12 md:px-2">
<h2 className="max-w-2xl text-balance font-serif text-4xl text-foreground leading-[1.05] tracking-tight sm:text-5xl md:text-6xl">
{title}
</h2>
<p className="mt-4 max-w-xl text-pretty text-base text-muted-foreground leading-relaxed sm:text-lg">
{description}
</p>
</header>
<div
className={cn(
"grid grid-cols-1 gap-2 px-2 md:auto-rows-[minmax(220px,1fr)] md:grid-cols-4 md:gap-1",
gridClassName,
)}
>
{available.map(({ source, testimonial }) => (
<div className={source.className} key={source.id}>
<TestimonialCard testimonial={testimonial} />
</div>
))}
</div>
</div>
</section>
);
}
A live X testimonial mosaic with resilient fallbacks
@tentui/testimonials-01
Files
import Image from "next/image";
import { buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils";
export function Cta01() {
return (
<section className="relative w-full bg-background font-sans text-foreground">
<div className="relative mx-auto w-full max-w-6xl">
<div className="flex flex-col gap-6 px-5 pt-12 pb-8 sm:px-6 md:px-8 lg:flex-row lg:items-start lg:justify-between lg:gap-10 lg:px-0">
<div className="flex flex-col gap-2 lg:max-w-md xl:max-w-lg">
<h2 className="text-balance font-normal text-[clamp(1.75rem,4.5vw,2.5rem)] leading-[1.15] tracking-[-0.02em]">
Don't let your customers leave without a Tkit.
</h2>
<p className="max-w-sm text-base text-muted-foreground">
Turn your website visitors into clients in minutes. No coding
required.
</p>
</div>
<div className="flex flex-col items-center gap-2 lg:mt-1">
<a
className={cn(
buttonVariants({ size: "lg" }),
"h-[51px] min-w-40 touch-manipulation px-6 transition-transform duration-150 ease-[cubic-bezier(0.23,1,0.32,1)] active:translate-y-0 active:scale-[0.97]",
)}
href="/login"
>
Start free trial
</a>
<p className="text-muted-foreground text-sm">
No credit card required.
</p>
</div>
</div>
<div className="overflow-hidden outline outline-border -outline-offset-1">
<Image
alt="Illustrated cloudy landscape with a boy looking toward distant mountains"
className="h-[220px] w-full object-cover object-bottom sm:h-[280px] md:h-[320px] lg:h-[360px]"
height={941}
sizes="(min-width: 72rem) 72rem, 100vw"
src="https://cdn.srb.codes/cta-01-landscape.svg"
unoptimized
width={1672}
/>
</div>
</div>
</section>
);
}
A responsive call-to-action with a wide landscape image
@tentui/cta-01
Files
import type { ReactNode } from "react";
import { cn } from "@/lib/utils";
import PrivacyPolicyPage, { PRIVACY_CLAUSES } from "./privacy/page";
const LEGAL_PAGES = ["Privacy"] as const;
const CORNER_POSITIONS = {
topLeft: "-top-2 -left-2",
topRight: "-top-2 -right-2",
bottomLeft: "-bottom-2 -left-2",
bottomRight: "-right-2 -bottom-2",
} as const;
function CornerMark({ position }: { position: keyof typeof CORNER_POSITIONS }) {
return (
<span
aria-hidden="true"
className={cn(
"pointer-events-none absolute z-10 flex size-4 items-center justify-center text-border",
CORNER_POSITIONS[position],
)}
>
<span className="absolute h-px w-full bg-current" />
<span className="absolute h-full w-px bg-current" />
</span>
);
}
function LegalNavigation() {
return (
<nav aria-label="Legal" className="py-8 md:sticky md:top-8 md:py-12">
<p className="font-mono text-[10px] text-muted-foreground uppercase tracking-[0.32em]">
Legal
</p>
<ul className="mt-4 flex flex-wrap gap-1 md:mt-6 md:flex-col md:flex-nowrap md:gap-0.5">
{LEGAL_PAGES.map((item) => {
const isActive = item === "Privacy";
return (
<li className="md:w-full" key={item}>
<span
aria-current={isActive ? "page" : undefined}
className={cn(
"relative flex items-center rounded-sm px-3 py-2 font-mono text-[13px] tracking-tight md:rounded-none md:py-1.5 md:pr-2 md:pl-4",
isActive
? "bg-primary/10 text-primary before:absolute before:inset-y-0 before:left-0 before:hidden before:w-px before:bg-primary md:before:block"
: "text-muted-foreground",
)}
>
{item}
</span>
{isActive ? (
<ol className="mt-3 ml-4 hidden flex-col gap-0.5 md:flex">
{PRIVACY_CLAUSES.map((clause) => (
<li key={clause.id}>
<a
className="block py-1.5 text-muted-foreground text-xs leading-snug transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50"
href={`#${clause.id}`}
>
{clause.title}
</a>
</li>
))}
</ol>
) : null}
</li>
);
})}
</ul>
</nav>
);
}
export interface LegalLayoutProps {
children?: ReactNode;
}
export default function LegalLayout({
children = <PrivacyPolicyPage />,
}: LegalLayoutProps) {
return (
<main className="min-h-svh bg-background px-4 py-12 text-foreground sm:px-6 lg:px-8 lg:py-16">
<div className="relative mx-auto w-full max-w-6xl border border-border border-dashed">
<CornerMark position="topLeft" />
<CornerMark position="topRight" />
<CornerMark position="bottomLeft" />
<CornerMark position="bottomRight" />
<div className="grid grid-cols-1 md:grid-cols-[12rem_minmax(0,1fr)]">
<aside className="border-border border-b border-dashed px-6 md:border-r md:border-b-0 md:px-5">
<LegalNavigation />
</aside>
<div className="min-w-0 px-6 py-10 md:px-10 md:py-12 lg:px-14">
{children}
</div>
</div>
</div>
</main>
);
}
A two-file App Router legal route group with sidebar navigation and numbered clauses
@tentui/legal-page-01
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
Files
"use client";
import { CheckIcon } from "lucide-react";
import { MotionConfig, motion } from "motion/react";
import { useState } from "react";
import { Badge } from "@/components/ui/badge";
import { buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import {
type AnimatedTab,
AnimatedTabs,
} from "@/components/animated-tabs";
type BillingCycle = "monthly" | "annual";
type PlanId = "starter" | "growth" | "scale";
type LimitKey = "workspaces" | "ticketsPerMonth" | "agentCredits";
type PlanLimits = Record<LimitKey, number>;
type PricingPlan = {
id: PlanId;
name: string;
tagline: string;
monthly: number;
annual: number;
unit: string;
popular?: boolean;
limits: PlanLimits;
features: readonly string[];
};
const UNLIMITED = Number.POSITIVE_INFINITY;
const EASE_OUT = [0.23, 1, 0.32, 1] as const;
const USD_PRICE_FORMATTER = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
maximumFractionDigits: 0,
});
const PLANS: readonly PricingPlan[] = [
{
id: "starter",
name: "Starter",
tagline: "For small businesses and startups.",
monthly: 12,
annual: 9,
unit: "/mo.",
limits: {
workspaces: 3,
ticketsPerMonth: 1_000,
agentCredits: 800,
},
features: [
"1,000 tickets every month",
"800 AI credits every month",
"3 workspaces",
"Real-time customer chat",
"Add more AI credits anytime",
],
},
{
id: "growth",
name: "Growth",
tagline: "For growing support teams.",
monthly: 19,
annual: 15,
unit: "/mo.",
popular: true,
limits: {
workspaces: 5,
ticketsPerMonth: 10_000,
agentCredits: 2_000,
},
features: [
"Everything in Starter",
"10,000 tickets every month",
"2,000 AI credits every month",
"Up to 5 workspaces",
"Add more AI credits anytime",
],
},
{
id: "scale",
name: "Scale",
tagline: "For established organizations.",
monthly: 39,
annual: 31,
unit: "/mo.",
limits: {
workspaces: UNLIMITED,
ticketsPerMonth: 50_000,
agentCredits: 10_000,
},
features: [
"Everything in Growth",
"50,000 tickets every month",
"10,000 AI credits every month",
"Unlimited workspaces",
"Priority support",
],
},
];
const LIMIT_ROWS: ReadonlyArray<{ label: string; key: LimitKey }> = [
{ label: "Workspaces", key: "workspaces" },
{ label: "Tickets / month", key: "ticketsPerMonth" },
{ label: "AI credits / month", key: "agentCredits" },
];
const MAX_SAVINGS_PERCENT = Math.max(
...PLANS.map((plan) => Math.round((1 - plan.annual / plan.monthly) * 100)),
);
const BILLING_TABS: readonly AnimatedTab[] = [
{ value: "monthly", label: "Monthly" },
{
value: "annual",
label: (
<span className="flex items-center gap-1.5">
Annual
<Badge variant="secondary">Save {MAX_SAVINGS_PERCENT}%</Badge>
</span>
),
},
];
const CTA_CLASS_NAME =
"touch-manipulation transition-[transform,background-color,color,box-shadow] duration-160 ease-[cubic-bezier(0.23,1,0.32,1)] active:scale-[0.97]";
function isUnlimited(value: number) {
return !Number.isFinite(value);
}
function formatLimit(value: number) {
if (isUnlimited(value)) return "Unlimited";
return value
.toLocaleString("en-US", {
notation: "compact",
maximumFractionDigits: 1,
})
.toLowerCase();
}
function formatPrice(value: number) {
return USD_PRICE_FORMATTER.format(value);
}
function checkoutHref(plan: PricingPlan, cycle: BillingCycle) {
return `/signup?plan=${plan.id}&billing=${cycle}`;
}
function GridTick({ className }: { className?: string }) {
return (
<span
aria-hidden="true"
className={cn(
"pointer-events-none absolute z-20 flex size-4 items-center justify-center",
className,
)}
>
<span className="absolute h-px w-full bg-foreground/20" />
<span className="absolute h-full w-px bg-foreground/20" />
</span>
);
}
function GridRect({ className }: { className?: string }) {
return (
<span
aria-hidden="true"
className={cn("pointer-events-none absolute z-20 size-1", className)}
>
<span className="block size-full bg-foreground/20" />
</span>
);
}
function PricingHeading({ level }: { level: "h1" | "h2" }) {
const className =
"text-balance font-normal text-[2.4rem] leading-[1.05] tracking-[-0.02em] sm:text-6xl";
const animation = {
initial: { opacity: 0, transform: "translateY(18px)" },
animate: { opacity: 1, transform: "translateY(0px)" },
transition: { duration: 0.55, ease: EASE_OUT },
};
if (level === "h1") {
return (
<motion.h1 className={className} {...animation}>
Choose your <em className="italic">pricing plan</em>
</motion.h1>
);
}
return (
<motion.h2
className={className}
initial={animation.initial}
transition={animation.transition}
viewport={{ once: true }}
whileInView={animation.animate}
>
Choose your <em className="italic">pricing plan</em>
</motion.h2>
);
}
export type Pricing01Props = {
headingLevel?: "h1" | "h2";
showLimits?: boolean;
};
export function Pricing01({
headingLevel = "h2",
showLimits = true,
}: Pricing01Props) {
const [cycle, setCycle] = useState<BillingCycle>("monthly");
const PlanHeading = headingLevel === "h1" ? "h2" : "h3";
return (
<MotionConfig reducedMotion="user">
<section className="relative w-full bg-background px-4 py-20 text-foreground sm:px-6 lg:px-8">
<div className="mx-auto flex w-full max-w-6xl flex-col gap-10">
<div className="mx-auto flex max-w-2xl flex-col gap-5 text-center">
<PricingHeading level={headingLevel} />
<motion.p
animate={{ opacity: 1, transform: "translateY(0px)" }}
className="mx-auto max-w-md text-balance text-base text-muted-foreground leading-relaxed"
initial={{ opacity: 0, transform: "translateY(14px)" }}
transition={{ duration: 0.55, delay: 0.08, ease: EASE_OUT }}
>
Flexible plans designed to scale with your support needs.
</motion.p>
</div>
<div className="flex justify-center">
<AnimatedTabs
aria-label="Billing cycle"
className="[&_[data-slot=animated-tabs-trigger]]:h-10 [&_[data-slot=animated-tabs-trigger]]:px-5"
layoutId="pricing-01-billing-indicator"
onValueChange={(value) => {
if (value === "monthly" || value === "annual") setCycle(value);
}}
tabs={BILLING_TABS}
value={cycle}
/>
</div>
<div className="flex flex-col gap-4">
<div className="relative grid grid-cols-1 border border-border bg-background text-foreground lg:grid-cols-3">
<GridTick className="-top-2 -left-2" />
<GridTick className="-top-2 -right-2" />
{showLimits ? null : (
<>
<GridTick className="-bottom-2 -left-2" />
<GridTick className="-right-2 -bottom-2" />
</>
)}
{PLANS.map((plan, index) => {
const price = cycle === "annual" ? plan.annual : plan.monthly;
return (
<motion.article
className={cn(
"relative flex flex-col overflow-hidden border-border py-4",
index < PLANS.length - 1 && "border-b lg:border-b-0",
index < 2 && "lg:border-r",
)}
initial={{ opacity: 0, transform: "translateY(12px)" }}
key={plan.id}
transition={{
duration: 0.4,
delay: index * 0.06,
ease: EASE_OUT,
}}
viewport={{ once: true, amount: 0.12 }}
whileInView={{
opacity: 1,
transform: "translateY(0px)",
}}
>
{plan.popular ? (
<div
aria-hidden="true"
className="pointer-events-none absolute inset-0"
style={{
background:
"radial-gradient(125% 125% at 50% 10%, var(--background) 40%, var(--primary) 100%)",
}}
/>
) : null}
<div className="relative z-10 flex flex-1 flex-col gap-5 p-6">
<div className="flex flex-col gap-5">
<div className="flex min-w-0 flex-1 flex-col gap-2">
<div className="flex w-full items-center justify-between gap-2.5">
<PlanHeading className="font-normal text-2xl text-foreground tracking-tight">
{plan.name}
</PlanHeading>
{plan.popular ? <Badge>Popular</Badge> : null}
</div>
<p className="text-muted-foreground text-sm leading-relaxed">
{plan.tagline}
</p>
</div>
</div>
<div className="flex items-baseline gap-1.5">
<span className="font-normal text-4xl text-foreground tracking-tight">
{formatPrice(price)}
</span>
<span className="text-muted-foreground text-sm">
{plan.unit}
</span>
</div>
<p className="h-4 text-muted-foreground text-xs">
{cycle === "annual" ? "billed annually" : ""}
</p>
<a
aria-label={`Get started with the ${plan.name} plan`}
className={cn(
buttonVariants({
variant: plan.popular ? "default" : "secondary",
}),
CTA_CLASS_NAME,
"my-4 w-full",
)}
href={checkoutHref(plan, cycle)}
>
Get Started
</a>
<ul className="flex flex-col gap-3">
{plan.features.map((feature) => (
<li
className="flex items-start gap-2.5 text-foreground/80 text-sm"
key={feature}
>
<CheckIcon
aria-hidden="true"
className={cn(
"mt-0.5 size-4 shrink-0",
plan.popular
? "text-primary"
: "text-foreground",
)}
/>
<span>{feature}</span>
</li>
))}
</ul>
</div>
</motion.article>
);
})}
</div>
{showLimits ? (
<div className="relative border border-border bg-background text-foreground">
<GridTick className="-bottom-2 -left-2" />
<GridTick className="-right-2 -bottom-2" />
<div className="relative grid grid-cols-1 bg-muted/30 md:grid-cols-4">
<GridRect className="-top-[2px] -left-[2px]" />
<GridRect className="-top-[2px] -right-[3px]" />
<GridRect className="-bottom-[2px] -left-[2px]" />
<GridRect className="-right-[3px] -bottom-[2px]" />
<div className="p-4 font-medium text-muted-foreground text-xs uppercase tracking-wide">
Limits
</div>
{PLANS.map((plan) => (
<div
className="hidden border-border border-l p-4 text-center font-medium text-foreground text-sm md:block"
key={plan.id}
>
{plan.name}
</div>
))}
</div>
{LIMIT_ROWS.map((row) => (
<div
className="grid grid-cols-2 border-border border-t md:grid-cols-4"
key={row.key}
>
<div className="col-span-2 p-4 text-foreground/80 text-sm md:col-span-1">
{row.label}
</div>
{PLANS.map((plan) => {
const value = plan.limits[row.key];
return (
<div
className="flex items-center justify-between border-border p-4 text-sm md:justify-center md:border-l"
key={plan.id}
>
<span className="text-muted-foreground text-xs uppercase tracking-wide md:hidden">
{plan.name}
</span>
{isUnlimited(value) ? (
<span className="flex items-center gap-1.5 text-foreground">
<CheckIcon
aria-hidden="true"
className="size-3.5"
/>
Unlimited
</span>
) : (
<span className="text-foreground/80">
{formatLimit(value)}
</span>
)}
</div>
);
})}
</div>
))}
</div>
) : null}
</div>
</div>
</section>
</MotionConfig>
);
}
Pricing plans with a billing toggle and usage comparison
@tentui/pricing-01
Files
"use client";
import { PlusIcon } from "lucide-react";
import { type ReactNode, useId } from "react";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { cn } from "@/lib/utils";
export interface FaqItem {
id: string;
question: string;
answer: ReactNode;
}
export interface Faq02Props {
title?: ReactNode;
description?: ReactNode;
faqs?: FaqItem[];
className?: string;
}
const FAQS = [
{
id: "lorem-ipsum",
question: "Lorem ipsum dolor sit amet?",
answer:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
},
{
id: "consectetur",
question: "Consectetur adipiscing elit sed do?",
answer:
"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
},
{
id: "occaecat",
question: "Excepteur sint occaecat cupidatat?",
answer:
"Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos.",
},
] satisfies FaqItem[];
export function Faq02({
title = "Everything you need to know",
description = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore.",
faqs = FAQS,
className,
}: Faq02Props = {}) {
const headingId = useId();
const defaultValue = faqs.length > 0 ? [faqs[0].id] : [];
return (
<section
aria-labelledby={headingId}
className={cn(
"w-full bg-background px-4 py-20 font-sans sm:px-6 sm:py-28",
className,
)}
>
<div className="mx-auto w-full max-w-5xl">
<header className="mx-auto flex max-w-2xl flex-col items-center text-center">
<h2
className="text-balance font-normal text-4xl text-foreground leading-[1.08] tracking-[-0.03em] sm:text-5xl"
id={headingId}
>
{title}
</h2>
<p className="mt-5 max-w-xl text-balance text-base text-muted-foreground leading-relaxed sm:text-lg">
{description}
</p>
</header>
<div className="relative mt-14 sm:mt-16">
<Accordion className="border-y" defaultValue={defaultValue}>
{faqs.map((faq) => (
<AccordionItem
className="border-border"
key={faq.id}
value={faq.id}
>
<AccordionTrigger className="group/faq-trigger [&>svg]:hidden! grid touch-manipulation grid-cols-[minmax(0,1fr)_2.25rem] items-center gap-x-4 rounded-none border-0 py-6 transition-none hover:no-underline sm:gap-x-5 sm:py-7">
<span className="text-left font-normal text-base text-foreground leading-6 sm:text-lg">
{faq.question}
</span>
<span className="flex size-9 items-center justify-center rounded-full border border-border bg-background text-muted-foreground shadow-xs transition-[transform,background-color,border-color] duration-160 ease-[cubic-bezier(0.23,1,0.32,1)] group-hover/faq-trigger:border-foreground/15 group-hover/faq-trigger:bg-muted group-active/faq-trigger:scale-[0.96] [&_svg]:size-4">
<PlusIcon
aria-hidden="true"
className="transition-transform duration-200 ease-[cubic-bezier(0.23,1,0.32,1)] group-aria-expanded/faq-trigger:rotate-45 motion-reduce:transition-none"
/>
</span>
</AccordionTrigger>
<AccordionContent className="pr-12 pb-7 sm:pr-16 sm:pb-8">
<div className="text-muted-foreground text-sm leading-relaxed sm:text-base">
{faq.answer}
</div>
</AccordionContent>
</AccordionItem>
))}
</Accordion>
</div>
<p className="mt-8 text-center text-muted-foreground text-sm">
Still curious?{" "}
<a
className="font-medium text-foreground underline decoration-border underline-offset-4 transition-[text-decoration-color] duration-160 ease-[cubic-bezier(0.23,1,0.32,1)] hover:decoration-foreground focus-visible:rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50"
href="mailto:hello@example.com"
>
Talk to our team
</a>
.
</p>
</div>
</section>
);
}
A numbered editorial FAQ section with focused disclosure rows
@tentui/faq-02
Files
"use client";
import { MinusIcon, PlusIcon } from "lucide-react";
import { type ReactNode, useId } from "react";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { cn } from "@/lib/utils";
export interface FaqItem {
id: string;
question: string;
answer: ReactNode;
}
export interface FaqSectionProps {
title?: ReactNode;
faqs?: FaqItem[];
className?: string;
}
const FAQS = [
{
id: "go-live",
question: "Lorem ipsum dolor sit amet?",
answer: (
<>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua.
</>
),
},
{
id: "tech-stack",
question: "Consectetur adipiscing elit?",
answer: (
<>
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi
ut aliquip ex ea commodo consequat.
</>
),
},
{
id: "visitor-messages",
question: "Sed do eiusmod tempor incididunt?",
answer: (
<>
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
dolore eu fugiat nulla pariatur.
</>
),
},
{
id: "brand-customization",
question: "Ut labore et dolore magna aliqua?",
answer: (
<>
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
officia deserunt mollit anim id est laborum.
</>
),
},
{
id: "domain-controls",
question: "Quis nostrud exercitation ullamco?",
answer: (
<>
Sed ut perspiciatis unde omnis iste natus error sit voluptatem
accusantium doloremque laudantium, totam rem aperiam.
</>
),
},
{
id: "free-trial",
question: "Excepteur sint occaecat cupidatat?",
answer: (
<>
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut
fugit, sed quia consequuntur magni dolores eos.
</>
),
},
] satisfies FaqItem[];
function GridTick({ className }: { className?: string }) {
return (
<span
aria-hidden="true"
className={cn(
"pointer-events-none absolute z-10 flex size-4 items-center justify-center",
className,
)}
>
<span className="absolute h-px w-full bg-border" />
<span className="absolute h-full w-px bg-border" />
</span>
);
}
function FaqBackdrop() {
return (
<div aria-hidden="true" className="pointer-events-none absolute inset-0">
<div className="absolute inset-0 bg-[linear-gradient(to_right,var(--color-border)_1px,transparent_1px),linear-gradient(to_bottom,var(--color-border)_1px,transparent_1px)] bg-size-[32px_32px] opacity-40 [mask-image:radial-gradient(ellipse_at_center,black,transparent_78%)]" />
<div className="absolute top-[12%] left-[8%] size-64 rounded-full bg-muted/80 blur-3xl" />
</div>
);
}
export function Faq01({
title = "Frequently asked questions",
faqs = FAQS,
className,
}: FaqSectionProps = {}) {
const headingId = useId();
return (
<section
aria-labelledby={headingId}
className={cn(
"w-full bg-background px-4 py-20 font-sans sm:px-6 sm:py-24",
className,
)}
>
<div className="relative mx-auto w-full max-w-6xl border-border border-y bg-background md:border-x">
<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="relative grid grid-cols-1 md:grid-cols-12">
<div className="relative flex min-h-64 flex-col items-start overflow-hidden border-border border-b p-8 md:col-span-5 md:min-h-0 md:border-r md:border-b-0 md:p-10 lg:p-12">
<FaqBackdrop />
<h2
className="relative max-w-md text-balance font-normal text-4xl text-foreground leading-[1.08] tracking-[-0.03em] sm:text-5xl"
id={headingId}
>
{title}
</h2>
</div>
<div className="relative md:col-span-7">
<GridTick className="-top-2 -left-2 hidden md:flex" />
<GridTick className="-bottom-2 -left-2 hidden md:flex" />
<Accordion>
{faqs.map((faq) => (
<AccordionItem
className="px-6 sm:px-8"
key={faq.id}
value={faq.id}
>
<AccordionTrigger className="group/faq-trigger [&>svg]:hidden! touch-manipulation items-center rounded-none py-6 transition-none hover:no-underline sm:py-7">
<span className="flex flex-1 items-center pr-4">
<span className="text-left text-base text-foreground leading-6 sm:text-lg">
{faq.question}
</span>
</span>
<span className="ml-auto flex size-8 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground transition-[transform,background-color] duration-160 ease-[cubic-bezier(0.23,1,0.32,1)] group-hover/faq-trigger:bg-muted/80 group-active/faq-trigger:scale-[0.97] [&_svg]:size-4">
<PlusIcon
aria-hidden="true"
className="group-aria-expanded/faq-trigger:hidden"
/>
<MinusIcon
aria-hidden="true"
className="hidden group-aria-expanded/faq-trigger:block"
/>
</span>
</AccordionTrigger>
<AccordionContent className="pr-10 pb-7 sm:pr-12">
<div className="text-muted-foreground text-sm leading-relaxed sm:text-base">
{faq.answer}
</div>
</AccordionContent>
</AccordionItem>
))}
</Accordion>
</div>
</div>
</div>
</section>
);
}
A split-layout FAQ section with subtle grid detailing
@tentui/faq-01
Files
"use client";
import { ArrowRight } from "lucide-react";
import {
MotionConfig,
motion,
useReducedMotion,
type Variants,
} from "motion/react";
import Image from "next/image";
import Link from "next/link";
import Marquee from "react-fast-marquee";
const ICON_CDN = "https://cdn.srb.codes/images/tech-stack-icons";
const EASE_OUT = [0.23, 1, 0.32, 1] as const;
const TECH_STACK: {
key: string;
title: string;
theme?: boolean;
}[] = [
{ key: "js", title: "JavaScript" },
{ key: "ts", title: "TypeScript" },
{ key: "python", title: "Python" },
{ key: "next-js", title: "Next.js", theme: true },
{ key: "express", title: "Express", theme: true },
{ key: "redis", title: "Redis" },
{ key: "sqlite", title: "SQLite" },
{ key: "node-js", title: "Node.js" },
{ key: "react", title: "React" },
{ key: "vue", title: "Vue.js" },
{ key: "shadcn-ui", title: "shadcn/ui", theme: true },
{ key: "tailwind", title: "Tailwind CSS" },
{ key: "motion", title: "Motion" },
{ key: "git", title: "Git" },
{ key: "postman", title: "Postman" },
{ key: "vite", title: "Vite" },
{ key: "aws", title: "AWS", theme: true },
{ key: "cfw", title: "Cloudflare Worker" },
{ key: "cf", title: "Cloudflare" },
];
const stagger = {
hidden: {},
visible: {
transition: {
staggerChildren: 0.05,
},
},
} satisfies Variants;
const fadeUp = {
hidden: {
opacity: 0,
transform: "translateY(12px) scale(0.98)",
},
visible: {
opacity: 1,
transform: "translateY(0px) scale(1)",
transition: {
duration: 0.4,
ease: EASE_OUT,
},
},
} satisfies Variants;
function AnimatedArrow({ className }: { className?: string }) {
return (
<div
data-slot="animated-arrow"
className={`size-6 -rotate-45 overflow-hidden duration-500 ${className ?? ""}`}
>
<div
data-slot="animated-arrow-track"
className="flex w-12 -translate-x-1/2 transition-transform duration-500 ease-in-out group-hover/animated-arrow:translate-x-0"
>
<span className="flex size-6">
<ArrowRight className="m-auto size-3" />
</span>
<span className="flex size-6">
<ArrowRight className="m-auto size-3" />
</span>
</div>
</div>
);
}
function TechIcon({ tech }: { tech: (typeof TECH_STACK)[number] }) {
if (tech.theme) {
return (
<>
<Image
alt={tech.title}
className="hidden [html.light_&]:block"
height={20}
src={`${ICON_CDN}/${tech.key}-light.svg`}
unoptimized
width={20}
/>
<Image
alt={tech.title}
className="hidden [html.dark_&]:block"
height={20}
src={`${ICON_CDN}/${tech.key}-dark.svg`}
unoptimized
width={20}
/>
</>
);
}
return (
<Image
alt={tech.title}
height={20}
src={`${ICON_CDN}/${tech.key}.svg`}
unoptimized
width={20}
/>
);
}
function TechCarousel() {
const reduceMotion = useReducedMotion();
return (
<div className="relative mx-auto w-full max-w-2xl">
<Marquee
autoFill
className="overflow-hidden"
gradient
gradientColor="var(--color-background)"
gradientWidth={80}
pauseOnHover
play={reduceMotion !== true}
speed={25}
>
{TECH_STACK.map((tech) => (
<div
className="mx-4 flex items-center gap-1.5 opacity-40 grayscale transition-opacity duration-200 ease-out hover:opacity-80 hover:grayscale-0"
key={tech.key}
>
<TechIcon tech={tech} />
<span className="text-muted-foreground text-xs">{tech.title}</span>
</div>
))}
</Marquee>
</div>
);
}
export function Hero01() {
return (
<MotionConfig reducedMotion="user">
<div className="max-w-screen overflow-x-hidden px-2">
<div className="screen-line-top screen-line-bottom mx-auto border border-line md:max-w-4xl">
<section className="relative overflow-hidden bg-background pt-28 pb-12 text-foreground md:pt-36 md:pb-20">
<div className="mask-[radial-gradient(75%_100%_at_top,black_45%,transparent_75%)] absolute inset-0 aspect-square opacity-65 starting:opacity-0 transition-opacity duration-700 ease-[cubic-bezier(0.23,1,0.32,1)] motion-reduce:starting:opacity-65 motion-reduce:transition-none md:aspect-9/4 dark:opacity-40 dark:motion-reduce:starting:opacity-40">
<Image
alt=""
className="object-cover object-top dark:hidden"
fetchPriority="high"
fill
priority
sizes="100vw"
src="https://cdn.srb.codes/images/hero-bg.avif"
unoptimized
/>
<Image
alt=""
className="hidden object-cover object-top dark:block"
fill
sizes="100vw"
src="https://cdn.srb.codes/images/hero-bg-dark.avif"
unoptimized
/>
</div>
<motion.div
animate="visible"
className="relative z-10 mx-auto w-full"
initial="hidden"
variants={stagger}
>
<div className="text-center">
<motion.div variants={fadeUp}>
<Link
className="group/animated-arrow mx-auto mb-8 inline-flex items-center gap-2 rounded-full border bg-muted py-1 pr-1 pl-3 text-sm shadow-md shadow-zinc-950/5 transition-[transform,background-color,border-color] duration-160 ease-[cubic-bezier(0.23,1,0.32,1)] hover:bg-background active:scale-[0.98] dark:border-t-white/5 dark:shadow-zinc-950 dark:hover:border-t-border"
href="#pricing"
>
<span className="relative flex size-2">
<span className="absolute inline-flex size-full animate-ping rounded-full bg-emerald-400 opacity-75 motion-reduce:hidden" />
<span className="relative inline-flex size-2 rounded-full bg-emerald-500" />
</span>
<span className="text-muted-foreground">
Available for new projects
</span>
<span className="block h-4 w-px bg-border" />
<AnimatedArrow className="rounded-full bg-background transition-colors duration-200 ease-out group-hover/animated-arrow:bg-muted" />
</Link>
</motion.div>
<motion.h1
className="font-semibold text-4xl leading-[1.1] tracking-tight sm:text-5xl md:text-6xl"
variants={fadeUp}
>
We build beyond
<br />
pretty pixels
</motion.h1>
<motion.p
className="mt-6 text-balance text-lg text-muted-foreground"
variants={fadeUp}
>
we build products that ship and scale
</motion.p>
<motion.div
className="mt-10 flex flex-col items-center justify-center gap-3 sm:flex-row"
variants={fadeUp}
>
<Link
className="inline-flex h-11 items-center justify-center rounded-lg bg-primary px-5 font-semibold text-primary-foreground text-sm shadow-[0_1px_0_rgba(255,255,255,0.24)_inset,0_18px_60px_rgba(0,0,0,0.12)] transition-[transform,opacity] duration-160 ease-[cubic-bezier(0.23,1,0.32,1)] hover:opacity-90 active:scale-[0.97]"
href="#contact"
>
Book a Call
</Link>
<Link
className="inline-flex h-11 items-center justify-center rounded-lg border border-border bg-background/45 px-5 font-semibold text-foreground/80 text-sm shadow-[0_1px_0_rgba(255,255,255,0.12)_inset] backdrop-blur transition-[transform,background-color,color] duration-160 ease-[cubic-bezier(0.23,1,0.32,1)] hover:bg-foreground/5 hover:text-foreground active:scale-[0.97]"
href="#pricing"
>
View Pricing
</Link>
</motion.div>
<motion.div className="mt-20" variants={fadeUp}>
<p className="mb-4 font-light text-muted-foreground/50 text-sm">
Production stacks, not prototypes
</p>
<TechCarousel />
</motion.div>
</div>
</motion.div>
</section>
</div>
</div>
</MotionConfig>
);
}
An animated hero with availability, service CTAs, and a technology carousel
@tentui/hero-01
Files
"use client";
import { ArrowLeft, RotateCcw } from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { parsePath } from "./parse";
import { type EditorStyle, VectorEditor } from "./vector-editor";
const NOT_FOUND_PATH = `
M 20 165 L 20 130 L 105 25 L 150 25 L 150 125 L 172 125 L 172 165 L 150 165 L 150 210 L 105 210 L 105 165 Z
M 105 125 L 105 82 L 70 125 Z
M 280 25 C 225 25 195 62 195 118 C 195 174 225 210 280 210 C 335 210 365 174 365 118 C 365 62 335 25 280 25 Z
M 280 72 C 302 72 314 88 314 118 C 314 148 302 163 280 163 C 258 163 246 148 246 118 C 246 88 258 72 280 72 Z
M 388 165 L 388 130 L 473 25 L 518 25 L 518 125 L 540 125 L 540 165 L 518 165 L 518 210 L 473 210 L 473 165 Z
M 473 125 L 473 82 L 438 125 Z
`;
const EDITOR_STYLE: EditorStyle = {
accent: "#0d99ff",
arm: "#8dbce0",
anchorR: 4,
handleR: 3.2,
pointFill: "var(--background)",
fill: "none",
fillOpacity: 0,
stroke: "#0d99ff",
strokeWidth: 1.25,
showRig: true,
fillRule: "evenodd",
};
export function NotFound01() {
const [path, setPath] = useState(() => parsePath(NOT_FOUND_PATH));
return (
<section className="relative isolate flex min-h-svh flex-col overflow-hidden bg-background text-foreground">
<div
aria-hidden="true"
className="pointer-events-none absolute inset-0 -z-10 bg-[linear-gradient(to_right,var(--border)_1px,transparent_1px),linear-gradient(to_bottom,var(--border)_1px,transparent_1px)] bg-size-[24px_24px] opacity-35 [mask-image:radial-gradient(ellipse_70%_62%_at_50%_40%,black,transparent)]"
/>
<main className="flex flex-1 flex-col items-center justify-center px-5 py-16 sm:px-8 sm:py-20">
<div className="relative w-full max-w-[40rem] px-4 sm:px-10">
<Button
className="absolute top-0 right-0 text-muted-foreground"
variant="ghost"
size="icon-sm"
aria-label="Reset vector"
title="Reset vector"
onClick={() => setPath(parsePath(NOT_FOUND_PATH))}
>
<RotateCcw />
</Button>
<VectorEditor
path={path}
onChange={setPath}
style={EDITOR_STYLE}
viewBox={[0, 0, 560, 235]}
width={560}
height={235}
className="h-auto w-full"
ariaLabel="Editable 404 vector"
/>
</div>
<div className="mt-14 flex max-w-xl flex-col items-center text-center sm:mt-16">
<h1 className="text-balance font-medium text-3xl tracking-[-0.04em] sm:text-5xl">
This page slipped off the canvas.
</h1>
<p className="mt-4 max-w-md text-pretty text-muted-foreground leading-6">
The layer you were looking for was moved, renamed, or never made it
past the first draft.
</p>
<Button
className="mt-7 transition-transform duration-150 ease-[cubic-bezier(0.23,1,0.32,1)] active:scale-[0.97] motion-reduce:transition-none"
size="lg"
nativeButton={false}
render={<a href="/" />}
>
<ArrowLeft data-icon="inline-start" />
Back to home
</Button>
</div>
</main>
</section>
);
}
An interactive 404 page with Figma-inspired vector controls and editable Bézier points
@tentui/404-01