Brightpixels is a JavaScript library for brighter-than-reference-white text, image highlights, UI shapes, surface glow, and particle effects on HDR-capable displays.
It uses extended-range WebGPU rendering for HDR output and falls back to ordinary colors when HDR rendering is unavailable.
Features
- Extended-range HDR output for text, images, shapes, UI surfaces, and particles.
- Ordinary-color fallbacks when HDR or WebGPU rendering is unavailable.
- Custom elements for text highlights, images, progress indicators, status shapes, and SVG geometry.
- Spotlight, ripple, edge-light, loading, selection, charge, trail, and sweep feedback for existing elements.
- Promise-aware loading, success, and error feedback.
- Hold, swipe, and drag bindings with pointer and keyboard input.
- Confetti, sparks, dots, element bursts, and pointer trails.
- Reduced-motion behavior across surface, gesture, progress, and particle effects.
- Global brightness and render-quality controls with capability diagnostics.
- TypeScript declarations and optional React components.
How To Use It
Installation
Install the package with npm:
npm install brightpixels
Import the core entry to register the custom elements and access the main JavaScript API:
import "brightpixels";
Or load the ES module directly in your HTML document:
<script type="module" src="https://cdn.jsdelivr.net/npm/[email protected]/index.js"> </script>
Import optional functionality through its package entry:
import {
trackAction,
bindHold,
bindSwipe,
bindDrag,
createEffectSequence,
} from "brightpixels/interactions";
import {
createParticleEffects,
} from "brightpixels/particles";
import {
createConfetti,
} from "brightpixels/confetti";
React projects can also import:
import "brightpixels/react"; import BrightConfetti from "brightpixels/react-confetti"; import BrightSurface from "brightpixels/react-surface";
Basic Usage
Use the custom elements directly after loading the core package:
<h1>
<bright-text intensity="12">
HDR heading
</bright-text>
</h1>
<bright-image intensity="8">
<img
src="/images/night-city.jpg"
alt="Streetlights reflected on a wet road">
</bright-image>
<bright-shape
shape="ring"
value="65"
color="#26df8b"
intensity="4"
role="progressbar"
aria-label="Upload progress"
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow="65">
</bright-shape>
Enhance Existing Text And Images
Use the JavaScript helpers when the content already exists in the page.
Use boost: "all" when every image color should receive the brightness multiplier.
Remote images require CORS permission before Brightpixels can read their pixels. Set crossorigin="anonymous" on the image and configure the image server to return an appropriate Access-Control-Allow-Origin header.
<h2 class="feature-heading">Night Mode</h2> <img class="hdr-photo" src="/images/chrome.jpg" alt="Reflective chrome object">
import {
brighten,
brightenImages,
} from "brightpixels";
const [heading] = brighten(".feature-heading", {
color: "#ff5900",
intensity: 10,
});
const [photo] = brightenImages(".hdr-photo", {
boost: "highlights",
intensity: 8,
});
heading.intensity = 7;
photo.intensity = 6;
Connect HDR Feedback To An Application Action
The interactions entry can follow application state while a Surface controller handles the light effect.
The signal passed to trackAction() stops Brightpixels feedback only. Pass an application-owned signal to fetch() when the request itself must be cancelled.
import {
brightenSurface,
} from "brightpixels";
import {
trackAction,
bindHold,
} from "brightpixels/interactions";
import {
createParticleEffects,
} from "brightpixels/particles";
const card = document.querySelector(".profile-card");
const button = document.querySelector("#save-profile");
const surface = brightenSurface(card, {
intensity: 9,
color: "#55eeff",
});
const particles = createParticleEffects();
const feedbackAbort = new AbortController();
const hold = bindHold(button, {
surface,
duration: 700,
onComplete() {
trackAction(
surface,
submitProfile(),
{ signal: feedbackAbort.signal }
)
.then(() => {
if (!feedbackAbort.signal.aborted) {
particles.burstFrom(button, {
count: 90,
shape: "spark",
});
}
})
.catch(showError);
},
});
Clean up controllers and listeners when the surrounding component is removed:
feedbackAbort.abort(); hold.destroy(); particles.destroy(); surface.destroy();
Confetti
Import the vanilla controller for a viewport confetti effect:
import {
createConfetti,
} from "brightpixels/confetti";
const confetti = createConfetti({
numberOfPieces: 180,
recycle: false,
intensity: 8,
});
await confetti.ready;
Restart or clear the current effect through the controller:
confetti.restart(); confetti.clear();
Call destroy() when the controller is no longer used:
confetti.destroy();
React Usage
BrightConfetti accepts the vanilla confetti options as component props:
import { useState } from "react";
import BrightConfetti from "brightpixels/react-confetti";
export function Celebration() {
const [active, setActive] = useState(false);
return (
<>
<button onClick={() => setActive(true)}>
Save
</button>
{active && (
<BrightConfetti
numberOfPieces={180}
recycle={false}
intensity={8}
onConfettiComplete={() => setActive(false)}
/>
)}
</>
);
}
Use BrightSurface when a React component needs a surface controller:
import { useRef } from "react";
import BrightSurface from "brightpixels/react-surface";
export function StatusCard() {
const light = useRef(null);
return (
<BrightSurface
ref={light}
as="section"
className="status-card"
options={{ intensity: 9 }}
>
<button
onClick={() => light.current?.flash("success")}
>
Confirm
</button>
</BrightSurface>
);
}
API Reference
API Methods
| Export | Description |
|---|---|
brighten(targets, settings?) | Wraps existing element contents with <bright-text> and returns BrightTextElement[]. |
brightenImages(targets, settings?) | Wraps matching images with <bright-image> and returns BrightImageElement[]. |
brightenEdges(targets, options?) | Applies edge-light overlays to compatible existing elements. |
brightenFeedback(targets, options?) | Attaches press and outcome feedback to existing elements. |
brightenSurface(element, options?) | Creates an interactive Surface controller for a connected HTML container. |
createSurfaceGroup(controllers) | Coordinates feedback across up to 32 Surface controllers. |
defineBrightpixels() | Registers the Brightpixels custom elements. Browser imports register them automatically. |
configureBrightpixels(options?) | Updates global Brightpixels rendering configuration. |
getBrightpixelsConfig() | Returns a copy of the current global configuration. |
getBrightpixelsCapabilities() | Returns a current browser-capability snapshot. |
version | Current package version string. |
`brighten()`
brighten(
targets,
{
intensity,
color,
}
);
targets(string | Element | Iterable<Element>): Elements whose text content should be wrapped.intensity(number, default16): HDR intensity from1to16.color(string, default"white"): CSS color applied to the Bright Text element.
`brightenImages()`
brightenImages(
targets,
{
intensity,
boost,
}
);
targets(string | Element | Iterable<Element>): Images to wrap.intensity(number, default16): HDR intensity from1to16.boost("highlights" | "all", default"highlights"): Brightens image highlights or the complete image.
Global Configuration
enabled(boolean, defaulttrue): Enables HDR renderers.falsereleases HDR renderer resources while fallback content stays visible.brightness(number, default1): Scales extra HDR light from0to1. Zero returns the HDR signal to reference intensity.quality("auto" | "high" | "low", default"auto"): Controls render resolution.highuses device pixel ratio up to 2x.lowuses up to 1x.autolimits large canvases near one million pixels with a minimum 1x scale.
import {
configureBrightpixels,
getBrightpixelsConfig,
} from "brightpixels";
configureBrightpixels({
brightness: 0.6,
quality: "auto",
});
console.log(getBrightpixelsConfig());
Capability Snapshot
getBrightpixelsCapabilities() returns:
webgpu(boolean): WebGPU API availability.hdr(boolean): Browser-side extended-range HDR capability signal.p3(boolean): Display P3 capability signal.reducedMotion(boolean): Currentprefers-reduced-motionstate.intersectionObserver(boolean):IntersectionObserveravailability.
These values describe browser capabilities. They do not measure physical display luminance.
“
intensity(number, default16): HDR signal multiplier from1to16.color(string, default"white"): CSS text color.mode("hdr" | "fallback" | null, read-only): Current renderer state.fallbackReason(read-only): Current fallback reason ornull.
Text stays selectable. The current renderer handles plain-text fragments up to 128 characters on one line.
“
intensity(number, default16): HDR signal multiplier from1to16.boost("highlights" | "all", default"highlights"): Highlight-only or full-image brightness amplification.image(HTMLImageElement | null, read-only): Current source image.mode("hdr" | "fallback" | null, read-only): Current renderer state.fallbackReason(read-only): Current fallback reason ornull.
“
Shape Properties
shape:ring,outline,bar,dot,line,arc,rect,pill,triangle,diamond,star,polygon, orpath. Default:ring.color(string): CSS foreground color. Default:whiteunless a status preset supplies a color.intensity(number, default16): HDR intensity from1to16.value(number, default100): Completion from0to100for rings, arcs, and bars.thickness(number, default4): Stroke width in CSS pixels.radius(number, default12): Corner radius for outlines and bars.points(string): Space-separatedx,ypairs in a0to100coordinate system.start-angle/startAngle(number, default-90): Arc start angle.sweep(number, default270): Arc extent from0to360degrees.d(string): SVG path data in a0 0 100 100coordinate system.filled(boolean): Fills a custom path as well as drawing its stroke.color-end/colorEnd(string): Optional second gradient color.angle(number, default0): Gradient direction in degrees.dash(string): Nonnegative SVG dash lengths separated by spaces or commas.linecap("round" | "butt" | "square", default"round"): Stroke line cap.track(string): Background rail for rings, arcs, and bars. A baretrackattribute uses#25252b.duration(number, default0): Progress transition duration from0to5000milliseconds.status: Empty string,loading,success,warning, orerror.indeterminate(boolean): Activates indeterminate loading motion on rings, arcs, and bars.
Shape Methods
shape.setStatus("success", {
pulse: true,
});
shape.pulse({
intensity: 8,
duration: 1200,
});
shape.stopPulse();
setStatus(status, options?): Applies or clears a status preset.{ pulse: true }runs one pulse for a terminal state.pulse(options?): Runs one brightness pulse. Peak intensity is clamped to1through16; duration is clamped to250through5000milliseconds.stopPulse(): Cancels the current pulse and restores the base intensity.
Edge Glow
`brightenEdges()` Options
color(string | null): Edge color. The computed top border color is used when omitted.intensity(number | null, default4): HDR intensity from1to16.thickness(number | null): Edge width from0.5to32CSS pixels. The current top border width supplies the default with a minimum of1.offset(number | null): Distance outside the border box from0to32CSS pixels.radius(number | null): Uniform corner radius in CSS pixels.trigger("always" | "hover" | "focus" | null): Controls edge visibility.
Edge Controller
edge.update(options); edge.refresh(); edge.destroy();
Readable properties:
targetmodefallbackReason
Interaction Feedback
`brightenFeedback()` Options
color(string): Overrides preset feedback colors.thickness(number): Edge width.press(boolean, defaulttrue): Activates automatic pointer and keyboard press feedback.
Feedback Controller
feedback.flash("success");
feedback.select(true);
feedback.cancel();
feedback.destroy();
flash() accepts:
presssuccesserrorwarningcompletenotify
Readable properties:
targetedge
Selection state controls light only. The application owns ARIA and application state.
Interactive Surfaces
Surface Options
color(string, default#55eeff): Base surface color.colorEnd(string, default empty): Optional second color.intensity(number, default8): HDR intensity from1to16.thickness(number, default2): Edge width from0.5to16CSS pixels.radius(number | null, defaultnull): Uniform pixel radius.nullreads the element’s computed top-left radius.spotlightSize(number, default180): Spotlight radius from24to800CSS pixels.spotlight(boolean, defaulttrue): Pointer and touch spotlight.ripple(boolean, defaulttrue): Press ripple.trail(boolean, defaultfalse): Fading pointer trail while the primary pointer is pressed.trailLifetime(number, default600): Trail lifetime from100to1500milliseconds.charge(number, default0): Application-controlled level from0to1.press(boolean, defaulttrue): Automatic pointer and keyboard feedback.loading(boolean, defaultfalse): Traveling loading light.selected(boolean, defaultfalse): Persistent selection light.enabled(boolean, defaulttrue): Enables this surface’s effects.
Surface Controller
surface.update(options);
surface.refresh();
surface.flash("success");
surface.ripple({ x: 80, y: 40 });
surface.sweep({ angle: 25, duration: 650 });
surface.setCharge(0.5);
surface.setLoading(true);
surface.select(true);
const unlink = surface.link(button, {
kind: "notify",
});
surface.cancel();
surface.destroy();
Controller methods:
update(options?): Merges supplied Surface options.refresh(): Rereads element geometry and styles.flash(kind?): Runspress,success,error,warning,complete, ornotifyfeedback.ripple(origin?): Starts a ripple at local border-box CSS coordinates. Omitted coordinates use the center.sweep(options?): Runs one directional sweep. Default angle:25degrees. Default duration:650ms. Duration is clamped from150to1500ms.setCharge(value?): Sets charge from0to1.setLoading(value?): Changes loading light.select(value?): Changes selection light.link(element, options?): Connects another element’s click to a Surface response and returns an unlink function.cancel(): Clears transient effects and loading while preserving selection and charge.destroy(): Removes the overlay, listeners, and owned renderer resources.
Readable properties:
targetoverlayreadymodefallbackReasonloadingselectedchargerunning
Surface Groups
createSurfaceGroup() accepts up to 32 distinct Surface controllers.
`burst()` Options
kind(default"press"): Surface flash type.stagger(number, default60): Delay between Surface responses, clamped from0to120milliseconds.from("start" | "center" | "end", default"center"): Selects the order used for the group response.
group.burst({
kind: "success",
stagger: 80,
from: "center",
});
group.cancel();
group.destroy();
pending(read-only): Number of delayed responses that have not started.cancel(): Cancels queued responses. Active light finishes normally.destroy(): Removes group listeners and timers. Member Surface controllers are not destroyed.
Interactions API
`trackAction()`
trackAction(
surface,
promise,
{
signal,
success,
error,
}
);
surface: ExistingBrightSurfaceController.promise: Promise or Promise-like application task.signal(AbortSignal): Stops Brightpixels feedback when aborted.success(Surface flash type orfalse, default"success"): Fulfillment feedback.error(Surface flash type orfalse, default"error"): Rejection feedback.
The returned promise keeps the original fulfillment value or rejection. An AbortError does not trigger error light.
Shared Gesture Options
bindHold(), bindSwipe(), and bindDrag() accept:
surface(BrightSurfaceController): Optional Surface whose charge follows gesture progress.signal(AbortSignal): Cancels active feedback and destroys the binding after abort.onProgress(progress): Receives values from0to1, then0after release or cancellation.onComplete(progress): Runs once after a completed gesture resets.onCancel(): Runs when an active gesture is cancelled.
Every binding exposes:
binding.active; binding.progress; binding.cancel(); binding.destroy();
`bindHold()`
Call bindHold() with a native <button>.
Additional options:
duration(number, default700): Hold duration from150to5000milliseconds.tolerance(number, default18): Pointer movement tolerance from4to100CSS pixels.
Pointer input, Space, and Enter start charging. Completion occurs after full charge and release. Assistive-technology click activation completes directly.
`bindSwipe()`
Call bindSwipe() with a native input[type="range"].
Pointer input completes after the range reaches its maximum and is released. Keyboard arrows, Home, End, Page Up, and Page Down change the value. Enter confirms an active gesture. Partial gestures reset to the input minimum.
`bindDrag()`
Call bindDrag() with an existing HTML region.
Additional option:
axis("x" | "y", default"x"): Drag direction.
Arrow keys move progress by 10%. Home and End select the bounds. Enter commits and Escape cancels. The application owns focusability, labels, roles, and ARIA values.
A horizontal drag temporarily uses touch-action: pan-y. A vertical drag uses touch-action: pan-x. destroy() restores the previous inline value when the binding still owns it.
Effect Sequences
createEffectSequence() accepts 1 to 64 steps. Each step is limited to five seconds and the complete sequence is limited to 30 seconds.
Available step structures:
{
effect: "wait",
duration
}
{
effect: "charge",
surface,
value,
duration?
}
{
effect: "sweep",
surface,
options?: {
angle?
},
duration?
}
{
effect: "flash",
surface,
kind?,
duration?
}
{
effect: "ripple",
surface,
options?: {
x?,
y?
},
duration?
}
{
effect: "group",
group,
options?: {
kind?,
stagger?,
from?
},
duration?
}
{
effect: "particles",
engine,
target,
options?,
duration?
}
Default durations:
charge:400ms.sweep:500ms.wait:100ms.flash:0ms.ripple:0ms.group:0ms.particles:0ms.
Sequence Controller
const sequence = createEffectSequence(steps);
const result = await sequence.play({
signal,
});
sequence.cancel();
sequence.destroy();
running(read-only): Indicates an active run.play({ signal? }): Resolves to"completed"or"cancelled". Starting another run cancels the previous one.cancel(): Cancels queued steps, clears transient effects on touched individual Surfaces, and restores their pre-run charge.destroy(): Cancels the run and releases sequence-owned listeners.
A sequence borrows Surface, group, and particle controllers. It does not destroy those controllers.
Particle API
Particle Engine Options
createParticleEffects(options?) accepts:
maxParticles(number, default1024): Active particle capacity from1to2048. Fallback rendering uses at most256.intensity(number, default6): Default HDR intensity from1to16.
const particles = createParticleEffects({
maxParticles: 1024,
intensity: 8,
});
`burst()` Options
x(number, default viewport center): Origin X coordinate in CSS pixels.y(number, default 75% of viewport height): Origin Y coordinate in CSS pixels.count(number, default120): Requested particle count. Reduced motion caps the active batch at12.colors(string[], default["#54efff", "#ff4ace", "#caff60", "#af78ff", "#ffb74c"]): Particle colors.shape("confetti" | "spark" | "dot", default"confetti"): Particle geometry.intensity(number): HDR intensity from1to16. The engine value is used when omitted.speed(number, default320): Initial speed in CSS pixels per second.velocityX(number): Exact horizontal velocity. Overrides the calculated horizontal velocity.velocityY(number): Exact vertical velocity. Overrides the calculated vertical velocity.wind(number, default0): Horizontal acceleration in CSS pixels per second squared.flutter(boolean, defaultfalse): Adds tumbling motion to rotating particles.opacity(number, default1): Alpha multiplier from0to1.angle(number, default-90): Direction in degrees.spread(number, default100): Directional spread from0to360degrees.gravity(number, default420): Vertical acceleration in CSS pixels per second squared.lifetime(number, default1800): Particle lifetime from100to10000milliseconds.size(number, default5): Base size from1to24CSS pixels.reducedMotion(boolean): Requests stationary fading particles. System reduced-motion preference always takes priority.
`burstFrom()`
burstFrom(element, options?) accepts the burst() options except x and y, plus:
edge("center" | "top" | "right" | "bottom" | "left", default"center"): Origin on the element.
Particle Controller
particles.burst(options); particles.burstFrom(element, options); const stopTrail = particles.trail( element, options ); particles.pause(); particles.resume(); particles.clear(); particles.destroy();
Readable properties:
canvasreadymodefallbackReasonactiveCountrunningmaxParticlesintensity
trail() defaults to window when no element is passed and returns an idempotent stop function. Trail emission responds to mouse pointer movement and stops under reduced motion.
Confetti API
Confetti Options
createConfetti() and <BrightConfetti /> use this option set:
numberOfPieces(number, default200): Concurrent amount during recycling or total amount for a one-shot. Maximum:2048.recycle(boolean, defaulttrue): Replaces expired pieces while the controller runs. Reduced motion uses one batch.run(boolean, defaulttrue): Controls emission and pause state.colors(string[], default["#54efff", "#ff4ace", "#caff60", "#af78ff", "#ffb74c"]): Confetti palette.confettiSource({ x, y, w, h }): Source rectangle in viewport CSS pixels. The full top edge is used when omitted.gravity(number, default0.1): Vertical acceleration in 60 Hz confetti units.wind(number, default0): Horizontal acceleration in 60 Hz confetti units.initialVelocityX(number | { min, max }, default4): Initial horizontal velocity. A number uses[-n, n].initialVelocityY(number | { min, max }, default10): Initial vertical velocity. A number uses[-n, 0].opacity(number, default1): Particle opacity from0to1.intensity(number, default8): HDR intensity from1to16.tweenDuration(number, default1500): Linear emission ramp in milliseconds.lifetime(number, default5000): Maximum lifetime from100to10000milliseconds.size(number, default5): Base size from1to24CSS pixels.shape("confetti" | "spark" | "dot", default"confetti"): Particle shape.onReady(controller): Runs once after initial renderer setup.onConfettiComplete(controller): Runs after a batch finishes naturally.
Confetti Controller
confetti.update(options); confetti.restart(); confetti.clear(); confetti.destroy();
update(options?): Replaces the complete option object. Omitted values return to defaults.restart(): Starts a new batch with the current options.clear(): Clears active pieces and stops the current batch.destroy(): Removes listeners, timers, canvas, and renderer resources.
Readable properties:
readycanvasmodefallbackReasonactiveCountemittedCountrunning
React Reference
`BrightConfetti`
BrightConfetti accepts every ConfettiOptions property.
Its ref exposes:
ref.current.restart(); ref.current.clear(); ref.current.activeCount; ref.current.emittedCount; ref.current.mode; ref.current.running;
The component works with React 18 and 19, renders nothing during SSR, and cleans up its canvas, timers, listeners, and renderer resources after unmount.
`BrightSurface`
BrightSurface accepts standard React HTML attributes plus:
as:div,section,article,aside,main,nav,header, orfooter. Default:div.options:BrightSurfaceOptions.
Its ref exposes:
ref.current.flash(kind); ref.current.ripple(origin); ref.current.sweep(options); ref.current.setCharge(value); ref.current.setLoading(value); ref.current.select(value); ref.current.cancel(); ref.current.target; ref.current.controller;
`brightpixels/react`
Importing:
import "brightpixels/react";
registers the custom elements, re-exports the core package API, and installs TypeScript JSX declarations for <bright-text>, <bright-image>, and <bright-shape>.
`brightpixelsready` Event
The core custom elements dispatch a bubbling brightpixelsready event when renderer mode or fallback reason changes.
document.addEventListener(
"brightpixelsready",
(event) => {
const {
kind,
mode,
reason,
version,
} = event.detail;
console.log(
kind,
mode,
reason,
version
);
}
);
Runtime event detail contains:
kind:text,image, orshape.mode:hdrorfallback.reason: Current fallback reason ornull.version: Brightpixels version.
Fallback reasons are:
disabledoffscreenwebgpu-unavailabledevice-lostmissing-imagerenderer-error
Alternatives And Related Resources
- Canvas UI: Fluid, Glass, and Shader Effects Over Live HTML
- Animated Border Glow Effect In Vanilla JavaScript – Border Beam Vanilla
- Add Canvas Confetti Effects to Any Website with Vanilla Confetti
- Canvas Particle Animation Library for Web Backgrounds – particles-js







