BD Cursor renders one overlay element as every author-driven cursor: a text LABEL beside the native pointer, an icon BADGE, a two-colour GRAPHIC that replaces the OS pointer, and a click-feedback HALO. Authoring is pure data-cursor-* attributes; the engine is assets/js/bd-cursor.js with companion styles in bd-cursor.css. Desktop pointer devices only — the CSS gates everything behind a hover/fine-pointer media query, and reduced-motion visitors keep the OS cursor.
Anatomy
| Axis | Mechanism | Example |
|---|---|---|
| Atom | data-cursor-label / data-cursor-badge / data-cursor-graphic |
data-cursor-badge="play" |
| Glyph slots | data-cursor-icon, data-cursor-icon-end |
data-cursor-icon="arrow-right" |
| Replace pointer | data-cursor-replace (boolean; graphic always replaces) |
data-cursor-replace |
| Native carve-out | data-cursor-native (boolean, wins over everything) |
data-cursor-native |
The component consumes design-system tokens only through namespaced --bd-cursor-* tokens with literal fallbacks, so it works on pages without design-system.css.
Configuration
Define window.bdCursorConfig before the script loads. Every key is optional.
| Key | Default | Meaning |
|---|---|---|
spritePath |
"/assets/images/svg-icons/_sprite.svg" |
Icon sprite for badge/label glyphs (currentColor, recolourable) |
cursorSpritePath |
"/assets/images/svg-cursors/_sprite.svg" |
Two-colour cursor sprite for GRAPHIC glyphs — ships in the package as dist/cursors.svg |
autoInjectMarkup |
false |
Create the overlay + halo elements when the page doesn't carry them |
suppressScrollBodyClass |
"is-animating" |
Skip scroll re-resolution while <body> carries this class (router transitions); null disables |
reducedMotion |
"hide" |
"hide" keeps the overlay DOM recoverable; "remove" strips it |
Both sprites must be served same-origin — <use href> does not load cross-origin.
Basic usage
<script>
window.bdCursorConfig = { autoInjectMarkup: true };
</script>
<script src="/assets/js/bd-cursor.js" defer></script>
<a href="/work" data-cursor-label="View work" data-cursor-icon-end="arrow-right">…</a>
<section data-cursor-badge="play" data-cursor-replace>…</section>
<div data-cursor-graphic="cursor-hand-pointing">…</div>
Without autoInjectMarkup, place the overlay markup in the page chrome before the scripts:
<div class="cursor-overlay" aria-hidden="true">
<div class="cursor-overlay-icon cursor-overlay-icon-lead"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" width="100%" height="100%"><use href=""/></svg></div>
<span class="cursor-overlay-text"></span>
<div class="cursor-overlay-icon cursor-overlay-icon-end"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true" width="100%" height="100%"><use href=""/></svg></div>
</div>
<div class="cursor-halo" aria-hidden="true"></div>
JavaScript
initBdCursor() runs automatically and is idempotent; destroyBdCursor() detaches all listeners and root flags so a client-side app can re-initialise.
| Signal | Direction | Purpose |
|---|---|---|
bd:before-nav (legacy alias studio:before-nav) |
listened | Clear a riding badge/graphic before a router swaps page content |
bd-cursor:refresh |
listened | Re-read the hovered element's data-cursor-* after runtime mutation (bd-video swaps play↔pause this way) |
Glyph contract: a badge/graphic value must resolve to a symbol in its sprite. A missing symbol renders nothing while the CSS still hides the native cursor — a cursorless region with no error. When pairing with BD Video, the icon sprite must contain play, pause, sound-off, sound-on, full-screen and arrow-top-right.
Accessibility
- The overlay is presentation only:
aria-hidden="true"on both elements,pointer-events: none, and no keyboard or screen-reader surface — all interaction semantics stay on the underlying elements. - Reduced-motion visitors keep the OS cursor; the engine never hides the native pointer for them.
- Must keep
data-cursor-nativeon precise controls inside replace regions (scrubbers, inputs) — the carve-out restores the OS pointer and works without consumer CSS. - The native-cursor hide is gated behind , which the engine adds only after confirming the overlay is live — no-JS visitors never lose their pointer.
Usage rules
Do
- Author through the data attributes; treat the engine as a black box
- Keep GRAPHIC glyphs in the cursor sprite and badge/label glyphs in the icon sprite — they have different colour models
- Dispatch
bd-cursor:refreshafter mutating a hovered element's cursor attributes
Don't
- Point a badge or graphic at a symbol that isn't in the sprite — verify against the sprite before shipping
- Use
data-cursor-replaceon an element with no glyph or label — the engine warns and keeps the overlay hidden - Re-theme by overriding rules — override the
--bd-cursor-*tokens instead
CSS reference
Ships as its own file (dist/css/bd-cursor.css), linked after design-system.css. Component tokens, all overridable:
| Token | Default | Used for |
|---|---|---|
var(--text-accent, #3485cd) |
Label box + badge fill | |
var(--text-inverted, #fffdfa) |
Label text + badge glyph | |
var(--text-primary, #1f1f1f) |
currentColor graphics |
|
var(--duration-2xs, 100ms) |
Overlay fade (JS reads the same token) | |
var(--duration-xs, 200ms) |
Halo press feedback | |
var(--ease-out, …) |
All transitions | |
--bd-cursor-tint-5/10/20 |
color-mix of the accent |
Halo fills and borders |
Structure classes the JS writes: and on the root, / / on the overlay, on icon slots, on the halo.
Use in another product
The design system installs once per product:
npm install github:bydefaultstudio/design-system-dist#semver:^4.7.0
This component's styles ship as dist/css/bd-cursor.css — copy it into the product's served assets and link it after design-system.css. Its behaviour ships as dist/js/bd-cursor.js — copy it into the product's served assets and include it once per page:
<script src="assets/js/bd-cursor.js" defer></script>