Skip to main content
Light Dark System

Thumbnail

<zn-thumbnail> | ZnThumbnail
Since 1.0 experimental

A captioned image tile — a fixed aspect-ratio preview with a title beneath it, optional corner badges and actions, and an optional full-screen preview that grows out of the thumbnail.

A thumbnail is a single media preview with its title beneath it. Thumbnails are usually placed inside a thumbnail group, which lays them out as a scrollable row that expands into a grid.

<div style="width: 200px;">
  <zn-thumbnail
    src="https://images.unsplash.com/photo-1439066615861-d1af74d74000?w=600&q=60"
    caption="Tahoe Day">
  </zn-thumbnail>
</div>

Examples

Aspect Ratio

The media frame is 16 / 9 by default. Set the aspect-ratio attribute to any CSS aspect ratio to change it — the image is cropped to fill the frame, so captions stay aligned across a row whatever the source dimensions are.

<div style="display: flex; gap: 16px;">
  <div style="width: 160px;">
    <zn-thumbnail
      src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=600&q=60"
      caption="16 / 9">
    </zn-thumbnail>
  </div>
  <div style="width: 160px;">
    <zn-thumbnail
      aspect-ratio="1 / 1"
      src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=600&q=60"
      caption="1 / 1">
    </zn-thumbnail>
  </div>
  <div style="width: 160px;">
    <zn-thumbnail
      aspect-ratio="3 / 4"
      src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=600&q=60"
      caption="3 / 4">
    </zn-thumbnail>
  </div>
</div>

The --zn-thumbnail-aspect-ratio custom property does the same thing, and because it inherits you can set it once on any ancestor — a wrapper, or a thumbnail group — to reshape every thumbnail beneath it. A thumbnail’s own aspect-ratio attribute still wins over an inherited value.

<div style="display: flex; gap: 16px; --zn-thumbnail-aspect-ratio: 1 / 1;">
  <div style="width: 160px;">
    <zn-thumbnail
      src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=600&q=60"
      caption="Inherited 1 / 1">
    </zn-thumbnail>
  </div>
  <div style="width: 160px;">
    <zn-thumbnail
      src="https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=600&q=60"
      caption="Inherited 1 / 1">
    </zn-thumbnail>
  </div>
  <div style="width: 160px;">
    <zn-thumbnail
      aspect-ratio="16 / 9"
      src="https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=600&q=60"
      caption="Overridden">
    </zn-thumbnail>
  </div>
</div>

Badges and Actions

The badge slot pins content to the bottom-left of the media and the actions slot pins it to the bottom-right. Use icon for the common case of a single badge icon.

Each slotted element becomes its own chip, so several actions read as separate icons dotted along the thumbnail rather than a single grouped pill. --zn-thumbnail-chip-gap controls the spacing between them, and --zn-thumbnail-chip-background / --zn-thumbnail-chip-color restyle them.

A badge describes the asset rather than doing something, so it keeps the chip’s shape but is held back on opacity (--zn-thumbnail-badge-opacity, default 0.65) and takes no pointer events — clicks pass straight through to the thumbnail. Actions keep full opacity, the pointer cursor and a hover state. --zn-thumbnail-chip-radius rounds both off.

The overlay sits outside the thumbnail’s link, so anything you put in actions keeps its own click behaviour — a download link downloads, a button’s handler runs — and never selects the thumbnail or follows its href.

<div style="display: flex; gap: 16px;">
  <div style="width: 180px;">
    <zn-thumbnail
      src="https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=600&q=60"
      caption="Sequoia Sunrise"
      icon="play_arrow">
    </zn-thumbnail>
  </div>
  <div style="width: 180px;">
    <zn-thumbnail
      src="https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=600&q=60"
      caption="One action"
      icon="play_arrow">
      <a slot="actions" href="https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05" download>
        <zn-icon src="download" size="14"></zn-icon>
      </a>
    </zn-thumbnail>
  </div>
  <div style="width: 180px;">
    <zn-thumbnail
      src="https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=600&q=60"
      caption="Three actions"
      icon="play_arrow">
      <zn-icon slot="actions" src="download" size="14"></zn-icon>
      <zn-icon slot="actions" src="share" size="14"></zn-icon>
      <zn-icon slot="actions" src="more_horiz" size="14"></zn-icon>
    </zn-thumbnail>
  </div>
</div>

Preview

Provide full-uri and the thumbnail becomes previewable: activating it opens a full-screen overlay that grows out of the thumbnail’s position, over a backdrop, with rounded edges. Escape, the close button, or a click on the backdrop shrinks it back to where it came from.

The panel takes the asset’s own aspect ratio, so it wraps the image rather than letterboxing it in a fixed-shape box — which also means the close button sits on the image’s top-right corner, not on a surrounding container. It grows to whichever of --zn-thumbnail-preview-max-width (70vw) or --zn-thumbnail-preview-max-height (70vh) it reaches first, so it always leaves the page visible around it.

The ratio is measured from the thumbnail’s own already-loaded image, which shares it with the full-size asset. That keeps the panel’s size known before the large file arrives, so the grow animation has a correct target from the first frame; the thumbnail image is held behind it, blurred, in the meantime, and the panel resizes only if the full asset turns out to be a genuinely different shape. Override the measurement with --zn-thumbnail-preview-aspect-ratio — for slotted media that can’t be measured, for instance — and round the corners with --zn-thumbnail-preview-radius.

<div style="display: flex; gap: 16px;">
  <div style="width: 180px;">
    <zn-thumbnail
      src="https://images.unsplash.com/photo-1439066615861-d1af74d74000?w=400&q=50"
      full-uri="https://images.unsplash.com/photo-1439066615861-d1af74d74000?w=2000&q=80"
      caption="Tahoe Day">
    </zn-thumbnail>
  </div>
  <div style="width: 180px;">
    <zn-thumbnail
      src="https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=400&q=50"
      full-uri="https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=2000&q=80"
      caption="Sequoia Sunrise">
    </zn-thumbnail>
  </div>
</div>

Because the preview opens in the browser’s top layer, it escapes a thumbnail group’s scrolling row rather than being clipped by it.

Preview Trigger

By default a plain click opens the preview. Set preview-trigger="button" to put a dedicated expand control on the media instead, leaving clicks free for selection or for the thumbnail’s href.

<zn-thumbnail-group caption="Pick a still" selectable>
  <zn-thumbnail
    value="tahoe"
    preview-trigger="button"
    src="https://images.unsplash.com/photo-1439066615861-d1af74d74000?w=400&q=50"
    full-uri="https://images.unsplash.com/photo-1439066615861-d1af74d74000?w=2000&q=80"
    caption="Tahoe Day"
    selected>
  </zn-thumbnail>
  <zn-thumbnail
    value="sequoia"
    preview-trigger="button"
    src="https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=400&q=50"
    full-uri="https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=2000&q=80"
    caption="Sequoia Sunrise">
  </zn-thumbnail>
  <zn-thumbnail
    value="sonoma"
    preview-trigger="button"
    src="https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=400&q=50"
    full-uri="https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=2000&q=80"
    caption="Sonoma Horizon">
  </zn-thumbnail>
</zn-thumbnail-group>

When both href and full-uri are set, a plain left click previews while modifier and middle clicks still open the link in a new tab. Cancelling zn-select suppresses the preview (and the navigation) entirely, and showPreview() / hidePreview() drive the overlay from script.

Previewing Other Media

The preview slot replaces the preview’s contents, so the full-size asset doesn’t have to be an image. Filling the slot enables the preview on its own — full-uri isn’t needed.

<div style="width: 180px;">
  <zn-thumbnail
    src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400&q=50"
    caption="Big Sur">
    <video slot="preview" controls autoplay muted
           poster="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1200&q=70"
           src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"></video>
  </zn-thumbnail>
</div>

Set href to render the thumbnail as an anchor. target is forwarded to it.

<div style="width: 180px;">
  <zn-thumbnail
    src="https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=600&q=60"
    caption="Goa Beaches"
    href="https://unsplash.com"
    target="_blank">
  </zn-thumbnail>
</div>

Selected

selected draws the accent ring. A thumbnail group with selectable manages this for you; set it by hand when you’re tracking selection yourself from the zn-select event.

A thumbnail only enters the tab order when it’s a link or when selectable is set — a group with selectable sets that on its children for you. If you’re handling zn-select yourself, set selectable too so the thumbnail is reachable by keyboard and announced as a button.

<div style="display: flex; gap: 16px;">
  <div style="width: 180px;">
    <zn-thumbnail
      src="https://images.unsplash.com/photo-1439066615861-d1af74d74000?w=600&q=60"
      caption="Selected"
      selected>
    </zn-thumbnail>
  </div>
  <div style="width: 180px;">
    <zn-thumbnail
      src="https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=600&q=60"
      caption="Not selected">
    </zn-thumbnail>
  </div>
</div>

Active

active draws a ring in a second colour, for the thumbnail currently in use — playing, open, being edited — as opposed to the one the user has picked. It’s independent of selected, and takes the ring colour when a thumbnail is both. Recolour either ring with --zn-thumbnail-active-color and --zn-thumbnail-selected-color.

<div style="display: flex; gap: 16px;">
  <div style="width: 160px;">
    <zn-thumbnail
      src="https://images.unsplash.com/photo-1439066615861-d1af74d74000?w=600&q=60"
      caption="Idle">
    </zn-thumbnail>
  </div>
  <div style="width: 160px;">
    <zn-thumbnail
      src="https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=600&q=60"
      caption="Selected"
      selected>
    </zn-thumbnail>
  </div>
  <div style="width: 160px;">
    <zn-thumbnail
      src="https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=600&q=60"
      caption="Active"
      icon="play_arrow"
      active>
    </zn-thumbnail>
  </div>
  <div style="width: 160px;">
    <zn-thumbnail
      src="https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=600&q=60"
      caption="Both"
      selected
      active>
    </zn-thumbnail>
  </div>
</div>

Disabled

A disabled thumbnail is dimmed, emits no zn-select, and renders as a plain element rather than a link.

<div style="width: 180px;">
  <zn-thumbnail
    src="https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=600&q=60"
    caption="Processing"
    href="/media/1"
    disabled>
  </zn-thumbnail>
</div>

Custom Media

Leave src unset and slot your own media into the image slot (or the default slot) — a <video>, a <canvas>, or an icon placeholder. Slotted media is stretched to fill the frame.

<div style="display: flex; gap: 16px;">
  <div style="width: 180px;">
    <zn-thumbnail caption="No preview">
      <zn-icon slot="image" src="image" size="32" color="disabled"></zn-icon>
    </zn-thumbnail>
  </div>
  <div style="width: 180px;">
    <zn-thumbnail caption="Colour block">
      <div slot="image" style="background: linear-gradient(135deg, #8967ef, #38bdf8);"></div>
    </zn-thumbnail>
  </div>
</div>

Without a Caption

Set hide-caption to render just the media frame.

<div style="width: 180px;">
  <zn-thumbnail
    src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=600&q=60"
    caption="Hidden"
    hide-caption>
  </zn-thumbnail>
</div>

Importing

If you’re using the autoloader or the traditional loader, you can ignore this section. Otherwise, feel free to use any of the following snippets to cherry pick this component.

To import this component from the CDN using a script tag:

<script type="module" src="https://cdn.jsdelivr.net/npm/@kubex/zinc@1.1.98/dist/components/thumbnail/thumbnail.js"></script>

To import this component from the CDN using a JavaScript import:

import 'https://cdn.jsdelivr.net/npm/@kubex/zinc@1.1.98/dist/components/thumbnail/thumbnail.js';

To import this component using a bundler:

import '@kubex/zinc/dist/components/thumbnail/thumbnail.js';

Slots

Name Description
(default) Fallback content rendered in place of the media when no src is set.
image Replaces the built-in <img> (e.g. a video, canvas or zn-icon).
caption Replaces the caption text.
badge Content pinned to the bottom-left of the media (e.g. a play indicator). Badges take no pointer events and are held back on opacity, so they read as information about the asset rather than as something to click.
actions Controls pinned to the bottom-right of the media (e.g. a download button). Each slotted element becomes its own chip, so several actions read as separate icons on the thumbnail rather than one grouped pill. This sits outside the thumbnail’s link, so buttons and links here behave normally and never select the thumbnail or follow its href.
preview Replaces the preview media (e.g. a <video> for the full-size asset).

Learn more about using slots.

Properties

Name Description Reflects Type Default
src Image URL for the built-in <img>. string -
alt Alternative text for the built-in image. Falls back to the caption. string -
caption The title shown beneath the media. string ''
aspectRatio
aspect-ratio
Aspect ratio of the media frame, as any CSS aspect-ratio value (e.g. 1 / 1, 4 / 3). Takes precedence over an inherited --zn-thumbnail-aspect-ratio. Defaults to 16 / 9. string ''
href Renders the thumbnail as a link to this URL. string -
target Where to open href (e.g. _blank). string -
fullUri
full-uri
URL of the full-size asset. Setting this enables the preview overlay, which grows out of the thumbnail’s position over a full-screen backdrop. string ''
previewTrigger
preview-trigger
How the preview opens. click (the default) opens it when the thumbnail is activated; button adds a dedicated expand control to the media instead, leaving clicks for selection or the link. 'click' | 'button' 'click'
previewLabel
preview-label
Accessible label for the built-in preview trigger and the preview overlay. string 'Preview'
value Identifier reported in the zn-select event detail and used for group selection. string ''
selected Draws the selected ring. Managed automatically inside a selectable zn-thumbnail-group. boolean false
active Draws the active ring — for the thumbnail currently in use (playing, open, being edited), as distinct from the user’s selection. Takes the ring colour when a thumbnail is both active and selected. Recolour it with --zn-thumbnail-active-color. boolean false
selectable Set by zn-thumbnail-group when it manages selection, so the thumbnail reads as clickable. boolean false
disabled Dims the thumbnail and blocks selection, navigation and preview. boolean false
icon Icon rendered in the bottom-left badge when the badge slot is empty. string ''
hideCaption
hide-caption
Hides the caption row, leaving just the media frame. boolean false
updateComplete A read-only promise that resolves when the component has finished updating.

Learn more about attributes and properties.

Events

Name React Event Description Event Detail
zn-select Emitted when an enabled thumbnail is activated by click or keyboard. Cancelable — call preventDefault() to stop the preview from opening and to stop a parent zn-thumbnail-group from changing its selection. -
zn-show Emitted when the preview overlay starts opening. -
zn-close Emitted once the preview overlay has finished closing. -

Learn more about events.

Methods

Name Description Arguments
showPreview() Opens the preview overlay. -
hidePreview() Closes the preview overlay. -

Learn more about methods.

Custom Properties

Name Description Default
--zn-thumbnail-aspect-ratio Aspect ratio of the media frame. Defaults to 16 / 9. The aspect-ratio attribute sets this on the thumbnail itself; set the property on any ancestor (including a zn-thumbnail-group) to apply it to every thumbnail beneath it.
--zn-thumbnail-radius Corner radius of the media frame.
--zn-thumbnail-preview-aspect-ratio Overrides the measured aspect ratio of the preview panel. By default the panel takes the asset’s own ratio so it wraps the image exactly.
--zn-thumbnail-preview-max-width Largest width the preview may take. Defaults to 70vw.
--zn-thumbnail-preview-max-height Largest height the preview may take. Defaults to 70vh.
--zn-thumbnail-preview-radius Corner radius of the preview panel.
--zn-thumbnail-chip-radius Corner radius of a badge, action or preview chip.
--zn-thumbnail-chip-background Background of a badge, action or preview chip.
--zn-thumbnail-chip-background-hover Hover background of an action or preview chip.
--zn-thumbnail-chip-color Foreground colour of a badge, action or preview chip.
--zn-thumbnail-chip-gap Gap between adjacent chips.
--zn-thumbnail-badge-opacity Opacity of a badge, holding it back from the action chips beside it. Defaults to 0.65.
--zn-thumbnail-selected-color Ring colour for the selected state.
--zn-thumbnail-active-color Ring colour for the active state.

Learn more about customizing CSS custom properties.

Parts

Name Description
base The component’s base wrapper.
link The anchor (or div) covering the media and caption.
media The fixed aspect-ratio media frame.
image The built-in image element.
overlay The layer holding the badge, actions and preview button.
badge The bottom-left badge container.
actions The bottom-right actions container.
preview-button The built-in preview trigger, when preview-trigger="button".
caption The caption beneath the media.
preview The preview <dialog>.
preview-backdrop The full-screen backdrop behind the preview.
preview-frame The rounded panel the preview media sits in.
preview-image The full-size image inside the preview.
preview-close The preview’s close button.

Learn more about customizing CSS parts.

Dependencies

This component automatically imports the following dependencies.

  • <zn-example>
  • <zn-icon>