Skip to content
← components

Glow Tile

new

A near-black app-icon tile with an aurora glow rising from the bottom — four stacked inset shadows plus an outer bloom. Recolors from one variable; scales to any size.

glow color
outer glow

hover to lift & brighten · every shadow scales with size

Usage

example.tsx
import { GlowTile } from "~/components/glow/glow-tile";
<GlowTile color="#2F6BFF" size={240} bloom intensity={1} radius={80}>
<Icon className="size-16 text-white/90" />
</GlowTile>

How the glow works

The tile is a dark vertical gradient with no glowing element inside — the light is faked entirely with inset shadows. An inset shadow with a negative Y offset paints its color along the bottom inner edge, so a stack of them builds a glow that rises from the floor of the tile.

Four layers do it: a deep saturated base (big blur, big negative spread), a lighter mid tone, a hot white core for the bright lip, and a faint top-edge tint. An outer bloom and a dark ambient shadow sit underneath for the halo. Every offset, blur, and spread is multiplied by --u (size / 400), so the whole effect scales cleanly, and all the hues are mixed from a single --glow-color.

glow-tile.css
.glow-tile {
--glow-size: 240; /* unitless → × 1px */
--glow-color: #2f6bff;
--u: calc(var(--glow-size) / 400); /* scale unit */
--glow-deep: color-mix(in srgb, var(--glow-color) 90%, #000);
--glow-soft: color-mix(in srgb, var(--glow-color) 55%, #fff);
width: calc(var(--glow-size) * 1px);
height: calc(var(--glow-size) * 1px);
border-radius: calc(80px * var(--u));
background: linear-gradient(180deg, #0a0909, #09101f);
box-shadow:
/* outer bloom + ambient */
0 calc(22px*var(--u)) calc(70px*var(--u)) calc(-12px*var(--u))
color-mix(in srgb, var(--glow-color) 45%, transparent),
0 calc(10px*var(--u)) calc(28px*var(--u)) calc(-6px*var(--u)) rgba(0,0,0,.55),
/* inner: top edge → white core → mid → deep base */
inset 0 calc(6px*var(--u)) calc(6px*var(--u)) calc(-2px*var(--u))
color-mix(in srgb, var(--glow-color) 60%, transparent),
inset 0 calc(-20px*var(--u)) calc(20px*var(--u)) calc(-6px*var(--u)) rgba(255,255,255,.4),
inset 0 calc(-40px*var(--u)) calc(30px*var(--u)) calc(-8px*var(--u))
color-mix(in srgb, var(--glow-soft) 50%, transparent),
inset 0 calc(-80px*var(--u)) calc(60px*var(--u)) calc(-30px*var(--u)) var(--glow-deep);
}

Props

proptypedefaultdescription
colorstring"#2F6BFF"Glow hue; deep/mid/bloom all derive from it.
sizenumber240Tile width = height in px; shadow geometry scales with it.
bloombooleantrueShow the outer bloom + ambient halo.
intensitynumber1Inner glow strength multiplier (≈0.4–1.4).
radiusnumber80Corner radius in 400-space (scales with size).
childrenReactNodeCentered contents (e.g. an icon).
…propsHTMLAttributesAny native div prop (className, style, …).

The full implementation, across 2 files. Copy it in or grab the zip. It leans on a cn() class helper and the theme tokens (--primary, --border, …).

components/glow/glow-tile.tsx
import { cn } from "~/lib/utils";
export interface GlowTileProps
extends React.HTMLAttributes<HTMLDivElement> {
/** Glow hue; the deep, mid, and bloom layers all derive from it. Default #2F6BFF. */
color?: string;
/** Tile size in px (width = height). Shadow geometry scales with it. Default 240. */
size?: number;
/** Show the outer bloom + ambient halo. Default true. */
bloom?: boolean;
/** Inner glow strength multiplier (≈0.4–1.4). Default 1. */
intensity?: number;
/** Corner radius in 400-space (scales with size). Default 80. */
radius?: number;
}
/**
* A near-black "app icon" tile with a colored glow rising from the bottom edge.
* The glow is four stacked inset shadows — deep base, lighter mid, a hot white
* core, and a subtle top edge — plus an outer bloom. Every shadow value scales
* off `--u` (size / 400) so the look holds at any size, and the whole thing
* recolors from a single `--glow-color`. Styling lives in `.glow-tile`
* (app.css); no Tailwind required.
*/
export function GlowTile({
color,
size,
bloom,
intensity,
radius,
className,
style,
...props
}: GlowTileProps) {
const vars = { ...style } as Record<string, string>;
if (color) vars["--glow-color"] = color;
if (size != null) vars["--glow-size"] = `${size}`;
if (bloom != null) vars["--glow-bloom"] = bloom ? "1" : "0";
if (intensity != null) vars["--glow-intensity"] = `${intensity}`;
if (radius != null) vars["--glow-radius"] = `${radius}`;
return (
<div
className={cn("glow-tile", className)}
style={vars as React.CSSProperties}
{...props}
/>
);
}
app/styles/app.css
/* ─── Glow tile (aurora icon) ──────────────────────────────────────── */
/* A near-black tile with a colored glow rising from the bottom edge, built
from four stacked inset shadows. Every value scales off --u (= size / 400),
so the look holds at any size, and the hue derives from --glow-color. */
.glow-tile {
--glow-size: 240; /* unitless number — multiplied by 1px below */
--glow-color: #2f6bff;
--glow-bloom: 1; /* 0 hides the outer bloom + ambient */
--glow-intensity: 1; /* scales the inner glow strength */
--glow-radius: 80; /* corner radius in 400-space (× --u) */
--u: calc(var(--glow-size) / 400);
--glow-deep: color-mix(in srgb, var(--glow-color) 90%, #000);
--glow-soft: color-mix(in srgb, var(--glow-color) 55%, #fff);
display: inline-flex;
align-items: center;
justify-content: center;
width: calc(var(--glow-size) * 1px);
height: calc(var(--glow-size) * 1px);
border-radius: calc(var(--glow-radius) * var(--u) * 1px);
background: linear-gradient(180deg, #0a0909, #09101f);
box-shadow:
/* outer bloom + ambient (gated by --glow-bloom) */
0 calc(24px * var(--u)) calc(75px * var(--u)) calc(-10px * var(--u))
color-mix(in srgb, var(--glow-color) calc(60% * var(--glow-bloom)), transparent),
0 calc(10px * var(--u)) calc(28px * var(--u)) calc(-6px * var(--u))
color-mix(in srgb, #000 calc(55% * var(--glow-bloom)), transparent),
/* 4. subtle top inner edge */
inset 0 calc(6px * var(--u)) calc(6px * var(--u)) calc(-2px * var(--u))
color-mix(in srgb, var(--glow-color) 60%, transparent),
/* 3. hot white core near the bottom (front-most) */
inset 0 calc(-22px * var(--u)) calc(22px * var(--u)) calc(-6px * var(--u))
color-mix(in srgb, #fff calc(min(100%, 60% * var(--glow-intensity))), transparent),
/* 2. lighter mid glow */
inset 0 calc(-44px * var(--u)) calc(34px * var(--u)) calc(-8px * var(--u))
color-mix(in srgb, var(--glow-soft) calc(min(100%, 78% * var(--glow-intensity))), transparent),
/* 1. deep saturated base glow (back-most) */
inset 0 calc(-90px * var(--u)) calc(66px * var(--u)) calc(-24px * var(--u))
color-mix(in srgb, var(--glow-deep) calc(min(100%, 100% * var(--glow-intensity))), transparent);
transition:
transform 0.25s cubic-bezier(0.2, 0.7, 0.2, 1),
box-shadow 0.25s ease,
filter 0.25s ease;
}
.glow-tile:hover {
transform: translateY(calc(-4px * var(--u)));
filter: brightness(1.08) saturate(1.05);
}
@media (prefers-reduced-motion: reduce) {
.glow-tile {
transition: none;
}
}