Components
Dia Browser's signature aurora glow — a rainbow field anchored to the bottom that rises up on mount via a scaleY transform, unfurling from the floor like an aurora. Zero dependencies, no canvas, no per-frame work. Ships four self-contained renderings of the same idea: DiaGradient (a bell-curve row of tall, heavily-blurred full-rainbow columns — the original footer look), PeakedGradient (stacked smooth bezier peaks, light front to dark back, one soft mountain of colour with a pointiness control and a scroll-driven reveal option), FoldGradient (the bar field on a 3D plane folded away from the viewer via CSS perspective and rotateX, like a glowing floor receding toward a horizon), and DodgeGradient (a vertical black-to-white fade color-dodge-blended over a horizontal rainbow, masked into a dome bloom). Each honours prefers-reduced-motion and is a drop-in background layer.
npx @21st-dev/cli@beta add arlanoska/dia-gradientLoading preview...
"use client"
import { useEffect, useRef, useState, type ReactNode } from "react"
import { DiaGradient } from "@/components/ui/dia-gradient"
// Palette ramps captured verbatim from the source playground (arlan.me).
const PALETTES: Record<string, { offset: number; color: string }[]> = {"Dia":[{"offset":0,"color":"#3B1115"},{"offset":0.0833,"color":"#303DA7"},{"offset":0.1667,"color":"#2055EB"},{"offset":0.25,"color":"#4782D8"},{"offset":0.3333,"color":"#9AB9DE"},{"offset":0.4167,"color":"#E2EBFB"},{"offset":0.5,"color":"#F0E0B5"},{"offset":0.5833,"color":"#FED42E"},{"offset":0.6667,"color":"#FA6A27"},{"offset":0.75,"color":"#FB2FB9"},{"offset":0.8333,"color":"#FD4FF6D8"},{"offset":0.9167,"color":"#FE94F96C"},{"offset":1,"color":"#FFC1FD00"}],"Ocean":[{"offset":0,"color":"#08122E"},{"offset":0.0833,"color":"#06389C"},{"offset":0.1667,"color":"#044DD8"},{"offset":0.25,"color":"#0C6CF3"},{"offset":0.3333,"color":"#1897E7"},{"offset":0.4167,"color":"#22B8DC"},{"offset":0.5,"color":"#61D1D9"},{"offset":0.5833,"color":"#97E7DD"},{"offset":0.6667,"color":"#9FD3E7"},{"offset":0.75,"color":"#8B96F6"},{"offset":0.8333,"color":"#8B81FFD4"},{"offset":0.9167,"color":"#A8BBFF6A"},{"offset":1,"color":"#C1E8FF00"}],"Sunset":[{"offset":0,"color":"#2A0A06"},{"offset":0.0833,"color":"#4F1410"},{"offset":0.1667,"color":"#681B15"},{"offset":0.25,"color":"#822219"},{"offset":0.3333,"color":"#BB2F1C"},{"offset":0.4167,"color":"#E63A1F"},{"offset":0.5,"color":"#FB721E"},{"offset":0.5833,"color":"#FDB316"},{"offset":0.6667,"color":"#FFC955"},{"offset":0.75,"color":"#FFA3AA"},{"offset":0.8333,"color":"#FF81DCEC"},{"offset":0.9167,"color":"#FFA3ED76"},{"offset":1,"color":"#FFC0FD00"}],"Aurora":[{"offset":0,"color":"#031018"},{"offset":0.0833,"color":"#094534"},{"offset":0.1667,"color":"#0C6046"},{"offset":0.25,"color":"#12805A"},{"offset":0.3333,"color":"#1BA874"},{"offset":0.4167,"color":"#21C888"},{"offset":0.5,"color":"#5BDCA1"},{"offset":0.5833,"color":"#86ECB9"},{"offset":0.6667,"color":"#94F1CD"},{"offset":0.75,"color":"#77E3DC"},{"offset":0.8333,"color":"#65D9E9EC"},{"offset":0.9167,"color":"#A3EAED76"},{"offset":1,"color":"#CFFAF200"}],"Candy":[{"offset":0,"color":"#FE7AB6"},{"offset":0.0833,"color":"#FE87BF"},{"offset":0.1667,"color":"#FE94C8"},{"offset":0.25,"color":"#FEA0D1"},{"offset":0.3333,"color":"#EFA3E0"},{"offset":0.4167,"color":"#DDA6F0"},{"offset":0.5,"color":"#C9A8FF"},{"offset":0.5833,"color":"#BBBEFF"},{"offset":0.6667,"color":"#ADD1FF"},{"offset":0.75,"color":"#C6E1E9"},{"offset":0.8333,"color":"#F2EFC0"},{"offset":0.9167,"color":"#FFF8D498"},{"offset":1,"color":"#FFFFFF00"}],"Ember":[{"offset":0,"color":"#1F090C"},{"offset":0.0833,"color":"#520F0D"},{"offset":0.1667,"color":"#70140E"},{"offset":0.25,"color":"#87170E"},{"offset":0.3333,"color":"#AE2B0F"},{"offset":0.4167,"color":"#D63D10"},{"offset":0.5,"color":"#F74B11"},{"offset":0.5833,"color":"#FF6E17"},{"offset":0.6667,"color":"#FF8D1D"},{"offset":0.75,"color":"#FFAE4C"},{"offset":0.8333,"color":"#FFD48A"},{"offset":0.9167,"color":"#FFECB8B1"},{"offset":1,"color":"#FFF6E000"}],"Mono":[{"offset":0,"color":"#0A1A4A"},{"offset":0.0833,"color":"#18328B"},{"offset":0.1667,"color":"#2141B7"},{"offset":0.25,"color":"#274EDA"},{"offset":0.3333,"color":"#2E59F8"},{"offset":0.4167,"color":"#466CFD"},{"offset":0.5,"color":"#5B7FFD"},{"offset":0.5833,"color":"#6B90FD"},{"offset":0.6667,"color":"#86A6FD"},{"offset":0.75,"color":"#A6BEFE"},{"offset":0.8333,"color":"#C0D3FEEC"},{"offset":0.9167,"color":"#D5E2FE77"},{"offset":1,"color":"#E8F0FF00"}]}
const PALETTE_NAMES = Object.keys(PALETTES)
const STYLE = `
.dgp { --page:#f4f4f5; --surface:#fff; --line:rgba(0,0,0,0.09); --ring:rgba(0,0,0,0.22);
--t1:#1b1b1b; --t2:#6b6b6b; --sel:#fcfcfc;
display:flex; min-height:100vh; width:100%; align-items:center; justify-content:center;
background:var(--page); color:var(--t1); padding:36px 24px;
font-family:-apple-system,BlinkMacSystemFont,"SF Pro Text",Inter,"Segoe UI",system-ui,sans-serif; }
.dgp-wrap { width:100%; max-width:680px; }
.dgp-preview { border:1px solid var(--line); border-radius:16px 16px 0 0; background:var(--surface);
height:340px; overflow:hidden; position:relative; }
.dgp-panel { border:1px solid var(--line); border-top:0; border-radius:0 0 16px 16px; background:var(--surface);
padding:16px 18px 18px; display:flex; flex-direction:column; gap:12px; }
.dgp-row { display:flex; align-items:center; gap:14px; }
.dgp-row > .lab { color:var(--t2); font-size:13px; width:84px; flex:0 0 auto; }
.dgp-slider { position:relative; margin-left:auto; flex:1; height:34px; border:1px solid var(--line);
border-radius:9px; background:var(--surface); display:flex; align-items:center; }
.dgp-slider input { -webkit-appearance:none; appearance:none; width:100%; height:34px; margin:0; background:transparent; cursor:pointer; }
.dgp-slider input::-webkit-slider-thumb { -webkit-appearance:none; appearance:none; width:3px; height:16px; border-radius:2px; background:var(--t1); opacity:.5; }
.dgp-slider input::-moz-range-thumb { width:3px; height:16px; border:0; border-radius:2px; background:var(--t1); opacity:.5; }
.dgp-slider .cap { position:absolute; left:12px; font-size:13px; color:var(--t2); pointer-events:none; }
.dgp-slider .val { position:absolute; right:12px; font-size:13px; color:var(--t2); font-variant-numeric:tabular-nums; pointer-events:none; }
.dgp-dd { position:relative; margin-left:auto; }
.dgp-dd-btn { display:flex; align-items:center; justify-content:space-between; gap:6px; height:34px; min-width:118px;
padding:0 10px 0 12px; border:1px solid var(--line); border-radius:9px; background:var(--surface); color:var(--t1);
font:inherit; font-size:13px; font-weight:500; cursor:pointer; outline:none; transition:border-color .15s ease; }
.dgp-dd-btn:hover { border-color:var(--ring); }
.dgp-dd-btn svg { color:var(--t2); transition:transform .18s ease; }
.dgp-dd-btn[data-open="true"] svg { transform:rotate(180deg); }
.dgp-dd-menu { position:absolute; top:calc(100% + 6px); right:0; z-index:20; min-width:168px;
background:var(--surface); border:1px solid var(--line); border-radius:12px; padding:4px;
box-shadow:0 1px 2px rgba(17,24,39,0.06), 0 8px 24px rgba(17,24,39,0.08); display:flex; flex-direction:column; gap:2px; }
.dgp-dd-opt { display:flex; align-items:center; width:100%; text-align:left; padding:8px 10px; border:0; border-radius:6px;
background:transparent; color:var(--t2); font:inherit; font-size:13px; cursor:pointer; transition:background .12s ease,color .12s ease; }
.dgp-dd-opt:hover { background:rgba(0,0,0,0.04); color:var(--t1); }
.dgp-dd-opt[data-selected="true"] { background:var(--sel); color:var(--t1); font-weight:500; }
`
function Chevron() {
return (<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><polyline points="6 9 12 15 18 9" /></svg>)
}
function Dropdown({ value, options, onChange, ariaLabel }: {
value: string; options: string[]; onChange: (v: string) => void; ariaLabel: string
}) {
const [open, setOpen] = useState(false)
const root = useRef<HTMLDivElement>(null)
useEffect(() => {
if (!open) return
const onDown = (e: MouseEvent) => { if (root.current && !root.current.contains(e.target as Node)) setOpen(false) }
document.addEventListener("mousedown", onDown)
return () => document.removeEventListener("mousedown", onDown)
}, [open])
return (
<div className="dgp-dd" ref={root}>
<button type="button" className="dgp-dd-btn" data-open={open ? "true" : "false"} aria-label={ariaLabel}
aria-expanded={open} role="combobox" onClick={() => setOpen((o) => !o)}>
<span>{value}</span>
<Chevron />
</button>
{open && (
<div className="dgp-dd-menu" role="listbox">
{options.map((o) => (
<button key={o} type="button" className="dgp-dd-opt" role="option" aria-selected={o === value}
data-selected={o === value ? "true" : "false"} onClick={() => { onChange(o); setOpen(false) }}>
{o}
</button>
))}
</div>
)}
</div>
)
}
function Slider({ label, value, min, max, step, format, onChange }: {
label: string; value: number; min: number; max: number; step: number
format: (v: number) => string; onChange: (v: number) => void
}) {
return (
<div className="dgp-row">
<span className="lab">{label}</span>
<div className="dgp-slider">
<input type="range" min={min} max={max} step={step} value={value} aria-label={label}
onChange={(e) => onChange(Number(e.target.value))} />
<span className="val">{format(value)}</span>
</div>
</div>
)
}
function RiseLoop({ children }: { children: ReactNode }) {
// loop the aurora rise so the effect is always visible on the card
const [up, setUp] = useState(false)
useEffect(() => {
const id = setInterval(() => setUp((u) => !u), 2100)
const t = setTimeout(() => setUp(true), 120)
return () => { clearInterval(id); clearTimeout(t) }
}, [])
return (
<>
<span data-ghost-open={up ? "true" : "false"} style={{ display: "none" }} />
<div style={{ position: "absolute", inset: 0, transformOrigin: "bottom",
transform: up ? "scaleY(1)" : "scaleY(0)",
transition: "transform 1100ms cubic-bezier(0.16, 1, 0.3, 1)" }}>
{children}
</div>
</>
)
}
export default function Default() {
const [palette, setPalette] = useState("Candy")
const [bars, setBars] = useState(9)
const [blur, setBlur] = useState(15)
const [peak, setPeak] = useState(0.98)
const [valley, setValley] = useState(0.55)
return (
<>
<style>{STYLE}</style>
<div className="dgp">
<div className="dgp-wrap">
<div className="dgp-preview">
<RiseLoop>
<DiaGradient stops={PALETTES[palette]} bars={bars} blur={blur} peak={peak} valley={valley} riseMs={0} />
</RiseLoop>
</div>
<div className="dgp-panel">
<div className="dgp-row">
<span className="lab">Palette</span>
<Dropdown ariaLabel="Palette" value={palette} options={PALETTE_NAMES} onChange={setPalette} />
</div>
<Slider label="Bars" value={bars} min={3} max={21} step={1} format={(v) => String(v)} onChange={setBars} />
<Slider label="Blur" value={blur} min={2} max={40} step={1} format={(v) => String(v)} onChange={setBlur} />
<Slider label="Peak" value={peak} min={0.4} max={1} step={0.01} format={(v) => Math.round(v * 100) + "%"} onChange={setPeak} />
<Slider label="Valley" value={valley} min={0.1} max={1} step={0.01} format={(v) => Math.round(v * 100) + "%"} onChange={setValley} />
</div>
</div>
</div>
</>
)
}
Loading preview...
Loading preview...
Loading preview...