Components/PrismGlow

PrismGlow

Spectral light cones that rise from an edge and flow left to right.

VerifiedSince 0.6.0

Installation

EXAMPLE.BASH
pnpm add @talex-touch/tuffex
EXAMPLE.TYPESCRIPT
import { TxPrismGlow } from '@talex-touch/tuffex/prism-glow'
import '@talex-touch/tuffex/prism-glow/style.css'
import '@talex-touch/tuffex/base.css' // tokens + resets, once per app

Usage

Card

The root is the card: put the background and radius on the root, and the light paints over that background and under the slot.

Loading demo...

Collapse on Grow

When the host grows, the light sinks into its edge in about 140ms instead of fading, and stays off until active is switched off and on again.

Loading demo...

Overlay

Without a slot, it sits behind a host's content, as in the CoreBox search bar. The host must be a stacking context; position the root absolutely at z-index: -1.

Loading demo...

Best Practices

  • Turn it on only while work is in progress: set active to true when it starts and back to false when it ends. Don't use it as permanent decoration.
  • When the glow sits in a fixed-height part, point growTarget at the outer container that grows; turn collapseOnGrow off only for hosts that grow while loading.
  • The component renders no text and announces nothing; pair it with visible status text or a role="status" region (sr-only is fine).
  • Put the background on the root or the host, never on the slot, or it covers the light.
  • Keep the main text away from the edge the light rises from, and use one per view.

API Reference

Props

NameTypeDefaultDescription
activebooleantrueOn / off; fades in over 0.3s, fades out over 0.45s, then unmounts.
palette'spectrum' | 'accent''spectrum'spectrum spans the whole wheel; accent uses neighbouring hues of --tx-color-primary.
placement'bottom' | 'top''bottom'The edge the light rises from; it flows left to right either way.
intensitynumber1Light-layer opacity, clamped to 0–1; non-finite counts as 1. The slot is unaffected.
durationnumber6Base flow period in seconds; smaller is faster. Falls back to 6 unless positive and finite.
collapseOnGrowbooleantrueSinks the light into its edge in about 140ms when the watched element grows over 8px.
growTargetHTMLElement | nullnullThe element collapseOnGrow watches; null means the component root.

Slots

SlotDescription
defaultContent rendered above the light layer; leave it out to use the component as an overlay.

CSS Variables

The props write these into the root's inline style; change them through the props:

VariableSourceDescription
--tx-prism-glow-intensityintensityLight-layer opacity, clamped to 0–1.
--tx-prism-glow-durationdurationBase flow period, with its s unit.

These are declared on the root, with defaults for light and dark surfaces:

VariableLightDarkDescription
--tx-prism-glow-l / --tx-prism-glow-c0.76 / 0.190.74 / 0.19oklch lightness and chroma of the cone colour.
--tx-prism-glow-l-core / -c-core / -a-core0.78 / 0.18 / 0.50.95 / 0.05 / 0.78Lightness, chroma, and alpha of the core along the edge.
--tx-prism-glow-a-halo0.220.3Alpha of the halo.
--tx-prism-glow-a-fringe0.30.44Alpha of the two dispersion fringes.
--tx-prism-glow-a-ray0.050.07Alpha of the vertical rays.
--tx-prism-glow-blendnormalplus-lighterBlend mode between cones.
--tx-prism-glow-reach11Cone height factor; 0.45 in high contrast, 0 on the leaving layer during a collapse.
  • To override them, use a selector more specific than the component's theme blocks (light is .tx-prism-glow, dark is :is([data-theme='dark'], .dark) .tx-prism-glow), and give dark its own rule.
  • --tx-pg-* are internal and not part of the API.

Overview

  • The root, div.tx-prism-glow, carries tx-prism-glow--{palette} and tx-prism-glow--{placement} (plus is-collapsing during a collapse) and isolation: isolate, making it its own stacking context; its position: relative sits in :where(), at zero specificity.
  • The light layer, .tx-prism-glow__field, is absolute; inset: 0; z-index: -1: it paints over the root's background and under the slot, is clipped to the root's radius, and is aria-hidden="true" with no pointer events.
  • While the light is on or fading out, it collapses once the watched element is more than 8px taller than its lowest height since the light came on, pinning the leaving layer to the pre-growth height. Shrinking and changes within 8px never trigger it, and after a collapse it stays off until active turns on again.
  • Dark is detected from an ancestor: under [data-theme='dark'] or .dark the dark tuning applies (near-white core, plus-lighter blending); otherwise the light tuning. There is no theme prop.
  • Under reduced motion, every animation and transition stops, the six cones hold one finished frame spread edge to edge, and the layer appears and disappears with active at once.
  • In high contrast (html.contrast, html[data-tx-contrast='high'], or prefers-contrast: more without html[data-tx-contrast='normal']), the cones shrink to a band along the edge and the rays are hidden.

Technologies

  • Motion is compositor-only (translate, scale, opacity); no keyframe reads a var() and there is no JS frame loop, so the flow stays smooth while the main thread is busy.
  • The look is inspired by Unicorn Studio's "New AI canvas loading state" clip, posted by George Hastings (@soulegit) on 2026-09-23. Only the look was borrowed; no code came from it.
  • Source: packages/tuffex/packages/components/src/prism-glow/, with the cone parameters in cones.ts.
View source
packages/tuffex/packages/components/src/prism-glow/index.ts

Use cases

  • A loading or working state on a card, input, search bar, or message composer.
  • The wait during a long AI generation (a canvas, an image, a long answer).
  • The "searching" state of the CoreBox search bar.
  • BorderBeam: a beam that travels along or breathes around a border; it draws the border, not cones rising from an edge.
  • ThinkingOrb: a standalone orb with its own label; to light up a card or input that is already there, use PrismGlow.
  • GlowText: a shine that sweeps across text or a card.