Adaptive metallic finishes for ordinary HTML elements.
MetalliCSS reads an element's geometry and a small set of CSS custom properties, then renders a metallic surface that follows the element. Content stays in the DOM, the effect works as progressive enhancement, and the package has no runtime dependencies.
The repository includes an interactive material lab in
index.html. Run it locally:
npm ci
npm run devThen open http://127.0.0.1:4173, or use the live Material Lab.
Important
The source tree on main is prepared as the 5.0.0 release candidate, but it
remains Unreleased until the matching Git tag and npm package are
published. The current npm release is still 4.0.3. Pin a published version
in production and consult the changelog before upgrading.
- Preserves semantic HTML while adding a generated Canvas surface.
- Supports declarative
.metallicssauto-initialization and explicit JavaScript lifecycle management. - Adapts to element dimensions and
border-radius. - Provides convex, flat, and concave treatments with eight built-in materials.
- Exposes color, lighting, intensity, and render-quality controls through CSS.
- Coalesces observed changes, skips equivalent renders, and cleans up generated resources deterministically.
Install the current published package:
npm install metallicss@4.0.3In the 5.0.0 candidate on main, importing the browser entry auto-initializes
current and future elements with the metallicss class. npm 4.0.3 initializes
matching elements that exist when the module runs; the same markup and CSS
remain compatible:
import "metallicss";<button class="metallicss gold-button" type="button">Deploy something shiny</button>.gold-button {
min-block-size: 3rem;
padding-inline: 1.25rem;
border: 0;
border-radius: 0.85rem;
color: #201500;
background: #d6a84b;
--metal: gold;
--convexity: 6;
}Always provide readable foreground and fallback background colors. The element should remain understandable if rendering is unavailable.
The renderer also reads the element's width, height, and computed
border-radius. Its computed background-color is used only when
--metal: neutral; other materials do not inherit it.
| Property | Accepted values and behavior | Default |
|---|---|---|
--metal |
silver, steel, chrome, gold, copper, lead, titanium, gunmetal, neutral, or another valid CSS color. neutral alone uses the computed background. |
silver |
--convexity |
Unitless depth clamped to -24…24; negative is concave, 0 is flat, positive is convex. |
4 |
--metallicss-color |
A valid CSS color that overrides the material color. Invalid values are ignored. | Material color |
--metallicss-angle |
Lighting direction in unitless degrees or deg, turn, rad, or grad; converted to degrees, then clamped to -3600…3600. |
90deg |
--metallicss-intensity |
Unitless strength clamped to 0…2. |
1 |
--metallicss-resolution |
Scale clamped to 0.25…3. If omitted, uses device pixel ratio capped at 2; invalid explicit values fall back to 1. Each Canvas dimension is capped at 2048 pixels. |
Automatic |
The four --metallicss-* properties are part of the Unreleased 5.0.0
candidate. The legacy --metal and --convexity controls remain the
compatibility path for npm 4.0.3.
All named exports in this section are part of the Unreleased 5.0.0 candidate
on main. There is no render alias and the preset export is plural:
presets.
import metallicss, { mount, observe, presets, refresh, unmount } from "metallicss";| Export | Contract |
|---|---|
metallicss(element, { force = false } = {}) |
Starts a render for one Element. Returns its generated HTMLCanvasElement, or null when no browser window, usable size, or Canvas 2D context is available. Throws TypeError for a non-element. Equivalent signatures are skipped unless forced. |
mount(element, options?) |
Renders and manages one element. Returns { element, render(force = true), destroy() }. Calling it again for the same element returns the existing controller. |
unmount(element) |
Destroys managed state and generated output. Returns true when anything was removed, otherwise false. |
refresh(root = document) |
Force-renders matching .metallicss elements under a document, fragment, or element. Returns the number matched. |
observe(root = document, options?) |
Mounts current and future matching descendants. Returns a disconnect function that also unmounts elements it owns; returns a no-op function when observation is unavailable. |
presets |
Frozen map of the eight built-in material names to their base colors. |
Rendering completes asynchronously after the generated SVG image loads. Listen for lifecycle events when subsequent work depends on the result.
| Option | Behavior | Default |
|---|---|---|
debounce |
Observer delay in milliseconds, clamped to 0…1000. |
48 |
observe |
Attach available mutation, resize, and visibility observers. | true |
visibility |
Pause observer-driven work while off-screen when IntersectionObserver exists. |
true |
import { mount } from "metallicss";
const card = document.querySelector(".product-card");
const controller = mount(card, { debounce: 32 });
controller.render(); // Forced by default; returns a Canvas or null.
controller.destroy(); // Idempotent cleanup.import { observe, refresh } from "metallicss";
const panel = document.querySelector("#results");
const disconnect = observe(panel);
refresh(panel); // Returns the number of matching elements.
disconnect();Prefer mount or observe for long-lived interfaces so cleanup is
explicit. Use metallicss for one-off rendering.
Events are dispatched on the rendered element.
| Event | detail |
|---|---|
metallicss:render |
{ duration, width, height }; the element also receives data-metallicss-ready. |
metallicss:error |
{ error } for Canvas or generated-image failures. |
The single-element renderer needs an attached browser Window, Canvas 2D,
SVG/image decoding, blobs, and object URLs. It does not require observer
APIs. Lifecycle helpers use feature detection: mount falls back from
ResizeObserver to the window resize event, visibility observation is
optional, and observe becomes a no-op without MutationObserver.
Treat the finish as progressive enhancement:
- Keep text, links, and controls as semantic HTML.
- Set accessible foreground and fallback background colors yourself.
- Do not use the finish as the only indication of state or meaning.
- Test your own Content Security Policy; generated image work uses
blob:object URLs and an embeddeddata:texture. - Destroy mounted renderers and disconnect subtree observers when their roots leave the application.
The automated browser suite covers Chromium, Firefox, and WebKit. That is test coverage, not yet a published browser-support guarantee; a versioned compatibility matrix is a roadmap gate.
The renderer coalesces observed changes, cancels stale image work, skips unchanged render signatures, caps device-pixel-ratio scaling, and limits each Canvas dimension to 2048 pixels.
npm run check:bundle measures dist/metallicss.min.js and enforces these
budgets:
| Encoding | Budget |
|---|---|
| Raw | 42,000 bytes |
| gzip -9 | 28,500 bytes |
| Brotli | 27,000 bytes |
The check prints the current measurements in CI. Raise a budget only with an intentional, reviewed size tradeoff.
MetalliCSS follows Semantic Versioning. Stable consumers
should use the npm release. Package metadata on main currently targets 5.0.0,
which remains a release candidate until its matching tag and npm package are
published.
See CHANGELOG.md for implemented user-visible changes and ROADMAP.md for future direction. The roadmap is not a promise of dates.
Contributions are welcome. Start with CONTRIBUTING.md, follow the Code of Conduct, and use the issue forms for reproducible bugs or concrete feature proposals.
- Questions and usage help: SUPPORT.md
- Vulnerability reports: SECURITY.md
- Project decision-making: GOVERNANCE.md
If MetalliCSS saves you time or makes a project more delightful, you can sponsor its maintenance on GitHub. Sponsorship never buys influence over security decisions, review standards, or the roadmap.
MIT © Mikael Åsbjørnsson-Stensland.