Lantern
Ambient.
The soft olive or amber corner glow from the Lantern hero, as a decorative background layer.
Hub network
A small world, lit up.
Publish a site from any in-game computer and it shows up in the directory.
import { Ambient } from "@/components/ui/ambient";
import { Button } from "@/components/ui/button";
import { Eyebrow } from "@/components/ui/eyebrow";
export default function AmbientDemo() {
return (
<div className="relative w-full max-w-2xl overflow-hidden rounded-lg border bg-background px-6 py-14 sm:px-10">
<Ambient position="top-right" intensity="strong" className="h-[420px] w-[420px]" />
<div className="relative">
<Eyebrow>Hub network</Eyebrow>
<h2 className="mt-4 font-display text-4xl leading-[1.06] font-medium tracking-[-0.05em] sm:text-5xl">
A small world, <em className="text-primary not-italic">lit up.</em>
</h2>
<p className="mt-4 max-w-[40ch] text-sm leading-relaxed text-muted-foreground">
Publish a site from any in-game computer and it shows up in the directory.
</p>
<Button className="mt-6">Open the directory</Button>
</div>
</div>
);
}Installation#
With the CLI
$npx lanterncn add ambientThis also installs the Lantern theme and any Lantern UI components it depends on. The shadcn CLI works directly too: npx shadcn@latest add httptim/lantern-ui/ambient. See Installation if your project is not set up yet.
Manually
Install the dependencies
npm install class-variance-authorityCopy the source into your project
components/ui/ambient.tsximport * as React from "react"; import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/lib/utils"; const ambientVariants = cva( "pointer-events-none absolute h-[700px] w-[600px] max-w-full bg-[radial-gradient(var(--ambient-color),transparent_65%)] select-none", { variants: { position: { "top-right": "top-0 right-0", "top-left": "top-0 left-0", "bottom-right": "right-0 bottom-0", "bottom-left": "bottom-0 left-0", top: "top-0 left-1/2 -translate-x-1/2", center: "top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2", }, tone: { olive: "", amber: "", }, intensity: { subtle: "", default: "", strong: "", }, }, compoundVariants: [ { tone: "olive", intensity: "subtle", className: "[--ambient-color:#71703917]" }, { tone: "olive", intensity: "default", className: "[--ambient-color:#71703926]" }, { tone: "olive", intensity: "strong", className: "[--ambient-color:#7170394d]" }, { tone: "amber", intensity: "subtle", className: "[--ambient-color:#f5a6650d]" }, { tone: "amber", intensity: "default", className: "[--ambient-color:#f5a66517]" }, { tone: "amber", intensity: "strong", className: "[--ambient-color:#f5a6652b]" }, ], defaultVariants: { position: "top-right", tone: "olive", intensity: "default" }, }, ); /** * The soft corner glow from the Lantern hero. A decorative, absolutely positioned layer: * put it inside a relative parent. Size it with className (it defaults to 600 by 700px). */ function Ambient({ className, position, tone, intensity, ...props }: React.ComponentProps<"div"> & VariantProps<typeof ambientVariants>) { return ( <div data-slot="ambient" aria-hidden="true" className={cn(ambientVariants({ position, tone, intensity }), className)} {...props} /> ); } export { Ambient, ambientVariants };Update the import paths to match your project
The source imports
cnfrom@/lib/utils.
Usage#
import { Ambient } from "@/components/ui/ambient";<section className="relative overflow-hidden">
<Ambient position="top-right" />
<h1 className="relative">...</h1>
</section>Ambient is absolutely positioned and hidden from screen readers. Put it inside a relative parent (add overflow-hidden to clip it) and keep the content after it positioned so it stays on top. Resize it with className, for example size-96.
Examples#
Positions, tones and intensity
top-left
olive / strong
olive / strong
center
amber / default
amber / default
bottom-right
amber / strong
amber / strong
import { Ambient } from "@/components/ui/ambient";
const panels = [
{ position: "top-left", tone: "olive", intensity: "strong" },
{ position: "center", tone: "amber", intensity: "default" },
{ position: "bottom-right", tone: "amber", intensity: "strong" },
] as const;
export default function AmbientPositions() {
return (
<div className="grid w-full max-w-2xl gap-4 sm:grid-cols-3">
{panels.map((p) => (
<div key={p.position} className="relative h-44 overflow-hidden rounded-lg border bg-background p-4">
<Ambient position={p.position} tone={p.tone} intensity={p.intensity} className="size-52" />
<div className="relative font-mono text-[10px] tracking-[0.2em] text-muted-foreground uppercase">
{p.position}
<br />
{p.tone} / {p.intensity}
</div>
</div>
))}
</div>
);
}