Cursors✨ Premium

Cursor Color Sampler

A 28 px white ring follows the pointer with no delay; on every mousemove, elementFromPoint identifies the hovered cell and copies its inline background into --sampler-color: the central disc and the glow (20 and 40 px) take the color, the glow within 150 ms. The grid hides the native pointer (cursor: none).

JSCSS VariablesColor

You'll get access to the interactive demo with a free account.

This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Cursors has 26 effects, including 1 free. Explore the category →

3 usage examples

Theme editor palette — 18 shades to hover — Cursor Color Sampler example 1

① Theme editor palette — 18 shades to hover

WhenSide panel of a design tool: the theme palette as a grid, a header and the current value.
WhyThe ring that takes on the color it hovers is a click-less eyedropper: the user sees the shade isolated from the rest of the palette, with its glow, before choosing. The immediate follow (no transition on the position) suits a precision tool.
SettingsGrid repeat(6, 1fr) × repeat(3, 1fr), ring reduced to 22 px, glow 0 0 14px / 0 0 28px, the cell's hex value displayed in the header from --sampler-color.
Paint color chart — pick a shade — Cursor Color Sampler example 2

② Paint color chart — pick a shade

WhenProduct page of a wall paint: grid of matte shades, range name, tester price.
WhyOn light, earthy shades, the colored glow around the ring gives an “open can” preview of the color; that is a color chart's selling point. Light cells make the dark border recommended under Accessibility a must here.
SettingsRing border #1c1917 (≥ 4.4:1 on every cell), wider glow 0 0 30px / 0 0 60px for a “light from the can” look, transition: box-shadow .25s.
Heatmap — reading cell by cell — Cursor Color Sampler example 3

③ Heatmap — reading cell by cell

WhenDashboard widget: attendance per hour on an 8 × 4 grid, from pale to deep blue.
WhyOn a heatmap, the ring isolates the cell under the pointer and its glow echoes its intensity — an aid for reading close shades that the eye tells apart poorly side by side. The value text remains essential (color alone is insufficient).
SettingsGrid repeat(8, 1fr) × repeat(4, 1fr), 20 px ring with a 2 px border, glow 0 0 12px / 0 0 24px; cells carry their value in a data-value displayed in the legend.

How it works

The .sampler-zone is a CSS grid of 5 columns × 3 rows (repeat(5, 1fr) / repeat(3, 1fr)), 100% × 300px, position: relative, cursor: none. Its fifteen .sampler-cell elements have neither content nor color CSS: each carries its background inline, style="background:#6366f1" (indigo, pink, amber, emerald, cyan, violet…). Measured: cells of 148.8 × 100 px in a 744 px zone. The first child, .sampler-cursor, is the ring: position: absolute, 28 px, border: 3px solid #fff, round, pointer-events: none, translate(−50%, −50%), z-index: 10.

The color travels through a CSS variable. The ring has box-shadow: 0 0 20px var(--sampler-color, rgba(255,255,255,.5)), 0 0 40px var(--sampler-color, rgba(255,255,255,.3)) with transition: box-shadow .15s, and its ::after (inset: 3px, round, opacity: .8) has background: var(--sampler-color, white). As long as the variable doesn't exist, white glow and white disc. On every mousemove, the script positions the ring with left/top (no transition on the position: immediate follow), shows it, then calls document.elementFromPoint(clientX, clientY). If the element found carries the sampler-cell class, it writes on the zone --sampler-color = target.style.background || target.style.backgroundColor — the inline value, returned by the browser as rgb(234, 179, 8) (measured on the yellow cell). The disc changes instantly, the glow within 150 ms.

Two details make this possible. The ring is pointer-events: none: without it, elementFromPoint would return the ring itself and the color would never change. And the script reads target.style, i.e. only the style attribute: a cell colored through a class or a stylesheet returns an empty string, the variable isn't updated and the ring keeps the previous color. The CSS also contains .sampler-cell { transition: filter .3s } while no rule ever changes filter — a transition with nothing to animate, already inert on the original page.

mouseenter shows the ring, mouseleave hides it (inline display: none) — the variable, for its part, keeps the last color. The shipped CSS sets display: none on .sampler-cursor: before the first hover, nothing is visible — without that initial state, the white ring and its white glow showed up in the grid's top-left corner, at −6/−6 px (measured). Since the native pointer is hidden by cursor: none, the ring is the only position cue inside the grid; it is white at 3 px, which matters for legibility on the light cells (see Accessibility).

Accessibility

  • prefers-reduced-motion: the only animated motion is the glow transition (150 ms); the ring's follow is instantaneous. Under @media (prefers-reduced-motion: reduce), set .sampler-cursor { transition: none } — and if you prefer to disable the effect, also remove cursor: none, otherwise the grid has no pointer at all.
  • cursor: none removes the native pointer over the whole grid: the ring replaces it, but it only exists if the script runs and is only correctly placed after the first mousemove. Without JavaScript, the grid is a hole with no pointer (the ring, at display: none, never appears). Declare cursor: none under a class set by the script (.js .sampler-zone).
  • Ring contrast: white over the fifteen cells, it measures 4.5:1 on the indigo #6366f1 but 1.9:1 on the yellow #eab308, 2.0:1 on the green #84cc16, 1.8:1 on the cyan #22d3ee — below the 3:1 threshold for a graphical indicator on seven cells out of fifteen. Switch the border to #0a0a0f (≥ 4.4:1 on all fifteen) or keep the white with a dark hairline: box-shadow: 0 0 0 1px #0a0a0f, 0 0 20px var(--sampler-color), ….
  • Color alone isn't enough (WCAG 1.4.1): the hovered cell is only designated by the glow's hue, and the cells are empty, nameless divs. If the swatch board is a real picker, give each cell role="button", tabindex="0" and an aria-label (“Indigo, #6366f1”), and display the sampled value as text (the hex code) next to the ring. On touch, no mousemove moves the ring: its initial display: none keeps it from sitting frozen in a corner.
  • Integration: the cells' backgrounds must be inline (style="background:…"), or replace the read with getComputedStyle(target).backgroundColor; the ring must be a child of the zone (zone.querySelector('.sampler-cursor')) and stay pointer-events: none; the CSS fixes the grid at 5 × 3 — adapt grid-template-columns/rows to your cell count, otherwise the extras create an implicit row. An explicit height is mandatory (cells have no height of their own). The reset on leave is done (display: none).

Browser compatibility

ES5 code (var, function). It requires document.elementFromPoint, CSS custom properties (var(), setProperty) and CSS grid. Zero dependencies.

Chrome 57+✓ Full
Firefox 52+✓ Full
Safari 10.1+✓ Full
Edge 16+✓ Full
Mobile iOS✓ Inert (no pointer)
Android Chrome✓ Inert (no pointer)

Without JavaScript, the color grid displays but the native pointer is hidden (cursor: none) and the ring, at display: none, never appears: no cursor inside the grid — reserve cursor: none for a class added by the script. Without var() (2016 browsers), the box-shadow and disc background declarations are invalid: white ring with no glow and no disc. Without CSS grid, the empty, heightless cells measure 0 px: nothing is visible.

The code

HTML structure to paste into your page (CSS + JS available with a premium account):

index.html — structure
<div class="sampler-zone" data-color-sampler>
  <div class="sampler-cursor"></div>
  <div class="sampler-cell" style="background:#6366f1;"></div>
  <div class="sampler-cell" style="background:#ec4899;"></div>
  <!-- … 15 in total, one per colour … -->
</div>
🔒 Unlock the full code — from €2.99 the first month

Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Grid (CSS — repeat(5, 1fr) / repeat(3, 1fr)) 5 columns × 3 rows Number of cells per row and per column. The CSS fixes the grid: with 18 cells, switch to repeat(6, 1fr), otherwise the last three form an implicit fourth row that squashes the height.
Cell colors (HTML — style="background:…") 15 vivid shades (#6366f1 … #a855f7) The script reads the style attribute: the color must be inline. Hex, rgb() or hsl() are accepted, the browser returns rgb(). An inline gradient also works as a glow (the whole string is copied), not as a disc color.
Ring (CSS — .sampler-cursor 28 px, 3 px #fff border) 28 px, white, 3 px Diameter and color of the grid's only pointer. White, it drops below 3:1 on light cells; #0a0a0f holds on all of them. The inner disc follows through inset: 3px: keep inset equal to the border width.
Glow (CSS — 0 0 20px … , 0 0 40px …, transition: box-shadow .15s) 20 px + 40 px, 150 ms Two drop shadows in the sampled color. Double the radii for a “lamp” look; 0 ms makes the change as instant as the disc. The rgba(255,255,255,.5) and .3 fallbacks apply before the first sample.
Central disc (CSS — ::after, opacity: .8, inset: 3px) sampled color at 80% Solid preview of the color. opacity: 1 for the exact color; remove the pseudo-element for a hollow ring that shows the cell through.
Color source (JS — target.style.background) the cell's style attribute Replace with getComputedStyle(target).backgroundColor to sample cells colored through a class — at the cost of one style computation per move, negligible on a grid.
cursor: none (CSS — .sampler-zone) native pointer hidden Essential for the ring to replace the arrow, dangerous without JavaScript. Declare it under a class set by the script; the ring's initial state is already covered by its display: none.

FAQ

Through the display: none of the .sampler-cursor rule. Absolutely positioned with no left/top, the ring sits at the grid's origin, shifted by half a diameter through translate(−50%, −50%) (−6/−6 px) — that is where it showed up, with its white fallback glow, as long as that initial state didn't exist (measured). The script writes display: block inline on mouseenter and mousemove, which beats the rule, then display: none on mouseleave. Remove the CSS declaration and the ring reappears in the corner from page load — on touch too, where no mousemove will ever move it.
Almost always the color source. The script reads target.style.background || target.style.backgroundColor, i.e. the cell's style attribute, nothing else. If your cells are colored through a class (.swatch-indigo { background: #6366f1 }), the read returns an empty string, setProperty writes an empty value and the ring keeps its previous color or its white fallback. Two fixes: put the color inline, or replace the read with getComputedStyle(target).backgroundColor. Also check that the element under the pointer is the cell itself: a child (text, icon) placed inside the cell makes the classList.contains('sampler-cell') test fail — use target.closest('.sampler-cell').
The sampled color is already available: zone.style.getPropertyValue('--sampler-color') returns rgb(234, 179, 8). Listen to click on the zone and copy that value into a field or a label; convert to hex if needed. For accessibility, each cell must become a control: role="button", tabindex="0", an aria-label with the color's name and code, and a :focus-visible style — the ring doesn't follow the keyboard. Display the current value as text next to the ring: color alone cannot be the only information.