Image

import "@taprootio/espalier/image";

<esp-image>

A responsive image with a native <picture> and an optional focal-point-aware banner mode.

esp-image accepts two authoring shapes:

  1. Project a light-DOM <picture> — the recommended form for published / server-rendered markup. Provide a real <picture> (or a bare <img>) as a child and esp-image projects it through a <slot>, adding only the layout box, border, and lightbox wiring. Because the image lives in the served HTML it is discoverable by the preload scanner and visible to crawlers, and it is fetched exactly once.
  2. Declare esp-image-option children — the editor / fallback form. esp-image builds a shadow <picture> and srcset from them. Use local-image when an editor needs a blob preview.

Source precedence is local-image → a projected <picture> / <img> → esp-image-option children. In every mode the host reserves its layout box up front from original-width / original-height, when supplied.

Once upgraded, the component measures its own width and updates sizes so a card or gallery column does not over-fetch. The sizes attribute covers the server-rendered initial fetch and the brief window before measurement:

For a long projected lazy-image list, defer-offscreen keeps the served sizes hint until an image approaches the viewport. This avoids replacing a small fallback selection with a larger candidate before the image can be seen. Eager projected images and every non-projected source keep the normal immediate measured-size behavior.

Banner mode gives all three source modes the same focal position, scrim, texture, and semantic overlay stack. ratio applies at wide widths and compact-ratio below the component-relative compact threshold.

banner-scheme sets the banner's polarity and defaults to the active scheme: a light page lightens the image behind dark text, a dark page darkens it behind light text. Fix either polarity explicitly when a banner must not follow the page scheme:

Headline, supporting copy, and one action

The overlay takes a headline in slot="overlay", an optional supporting sentence in slot="overlay-supporting", and at most one native link or button in slot="overlay-action". Together they form one copy stack: grouped spacing, the supporting sentence on the lead type role so the headline stays prominent, line length bounded by --esp-image-overlay-measure (36rem), and an action drawn in the overlay ink with a focus ring that reads over any photograph. The scrim covers the whole stack. middle-start and middle-end center the copy against an inline edge, and an auto scrim follows it to that edge:

A headline on its own lays out exactly as it always has.

Copy below the image at compact widths

A crop that keeps the subject at narrow widths can lose the quiet region the copy sat on. compact-placement="below" moves the copy into a band under the image below the compact width, painted with the banner scheme's own surface and ink. Narrow this example's container to see it switch:

Long copy, long words, and enlarged text grow the banner rather than being clipped. Copy never moves with the image: motion applied to the projected image or ::part(image) stays inside the image box, and stops under prefers-reduced-motion. Server-rendered banners can lay all of this out before the component upgrades with the static stylesheet @taprootio/espalier/image/banner-static.css; the Image banners guide covers its inputs and limits.

The scrim follows its content

A directional scrim holds full strength over at least the first 20% of the frame, and further when the overlay content reaches past it, so every line of a long heading on a short card cover sits on the full-strength scrim. A radial scrim grows an opaque core that covers centered content. Overlays that already fit the 20% band render exactly as before.

Meeting an adjoining band

A directional scrim is capped by scrim-strength, so even strong leaves roughly a quarter of the photograph showing at the very edge it is anchored to. scrim-edge="flush" paints that edge at full alpha and relaxes to the ordinary strength over --esp-image-scrim-edge-depth (12% of the gradient axis by default), so a banner dissolves into a section band directly beneath it with no seam. Above that band the scrim is unchanged — flush is an edge treatment, not a heavier veil over the subject.

The join is exact because both sides paint the same token: when banner-scheme resolves to the page scheme the scrim ink is --esp-color-background, which is what esp-section paints its band with. Put the banner in a section with no padding, directly above a section in the same context:

flush is inert unless the resolved scrim is directional — an auto scrim that resolves to top or bottom picks it up, while none, flat, and radial ignore it, having no anchored edge for it to close.

It is its own attribute rather than a fourth scrim-strength because the two are independent: scrim-strength is how much veil sits over the subject, scrim-edge is whether the anchored edge completes. A light veil with a closed edge is exactly the composition this exists for, and a strength step could not express it.

A seamless tile can bleed across the join too — any background-image value, a tile the application serves, a data URI, or a gradient. Hand the edge the same background the adjoining section paints and it tiles up over the photograph, fading out over the same band:

The component anchors its own side of the tile to the scrim's edge, so the banner's last tile row ends exactly at the join. Continuity into the section below is the consumer's half of the contract: the same background-size, and a top-anchored background-position on the adjoining band.

Every procedural preset supports fine, medium, and coarse scales.

Dots

A uniform tiled ink screen (shown at fine):

Halftone

The dot screen ramping toward the content edge (shown at medium):

Paper

Soft fractal-noise mottle, like printed stock (shown at coarse):

Grain

High-frequency film grain that also kills banding (shown at fine):

Grunge

Thresholded turbulence, a distressed ink wash (shown at medium):

Scanlines

Hairline rules along the block axis (shown at coarse):

Duotone

The photo mapped onto two theme-derived hues:

Consumer texture tokens

Any background-image value — a tile the application serves, a data URI, or a gradient — can be wired directly through the public consumer-image token path (the optional @taprootio/espalier/textures/banner-textures.css stylesheet ships .esp-texture-paper/-grain/-grunge classes with the tiles inlined as data URIs, so nothing needs asset configuration):

Registered textures

Wiring those tokens on every banner is verbose and not reusable. The application's texture vocabulary lives on esp-root: register the look once — registerImageTexture(name, definition) from the module, or the root's registerTexture static in script-only contexts — and any banner under the root selects it by name. Live banners re-render on registration, so markup order and registration order never race, and the root's registeredTextures() enumerates the presets for an editor's texture picker:

Slots

<esp-image> has the following slots:

Default

A projected native image, or esp-image-option children. When projecting, alt lives on the consumer's <img>; caption remains the lightbox text.

overlay

The banner headline (or any semantic overlay content) above the image and decorative layers.

overlay-supporting

One supporting sentence under the headline. With it, or an action, the overlay becomes a copy stack.

overlay-action

One native a or button under the copy. More than one logs a warning.

Attributes

<esp-image> has the following attributes:

original-height

original-height has a default value of 0.

Original image height, used with originalWidth to reserve the intrinsic aspect ratio and report portrait orientation.

original-width

original-width has a default value of 0.

Original image width, used with originalHeight to reserve the intrinsic aspect ratio and report portrait orientation.

low-res

low-res has a default value of "".

Fallback image URL used as the internal <img src> when responsive options build a shadow <picture>.

local-image

local-image has a default value of "".

Local preview URL, commonly a blob: URL. It takes priority over projected content and responsive options so an editor can show a new upload at once.

caption

caption has a default value of "".

Lightbox caption in every mode, and internal image alternative text for local-image and responsive-option modes.

sizes

sizes has a default value of "100vw".

Initial/server-side responsive-image size hint. After upgrade the component replaces this with its measured rendered width.

loading

loading has a default value of "eager".

Native loading strategy for internally rendered images. Projected images retain the consumer's own loading attributes.

defer-offscreen

defer-offscreen has a default value of false.

For lazy projected images, defer measured sizes upgrades until the image approaches the viewport. A 200px prefetch margin keeps the full-size candidate ready before it is visible. Eager projections and internally rendered images keep their normal immediate measurements.

banner

banner has a default value of false.

Enable borderless banner defaults and the decorative/content layer stack.

banner-scheme

banner-scheme has a default value of "auto".

Banner polarity. dark darkens the image behind light text; light lightens it behind dark text. auto (the default) follows the active scheme, so a light page lightens and a dark page darkens.

scrim

scrim has a default value of "auto".

Legibility-layer anchor. auto follows overlay presence and position.

scrim-strength

scrim-strength has a default value of "medium".

Preset opacity for the scrim.

scrim-edge

scrim-edge has a default value of "none".

Edge treatment for a directional scrim. flush paints the scrim ink at full alpha along the edge the scrim is anchored to, relaxing to the ordinary strength over --esp-image-scrim-edge-depth, so a banner can dissolve into a section band it adjoins. Inert unless the resolved scrim is top, bottom, left, or right.

texture

texture has a default value of "none".

Optional texture: a built-in procedural preset, or the name of a texture registered with registerImageTexture. Unknown names render as none (and recover if the name is registered later).

texture-scale

texture-scale has a default value of "medium".

CSS pitch or discrete SVG-filter variant for the selected texture.

content-position

content-position has a default value of "bottom-start".

Logical position for semantic banner content. middle-start and middle-end center the copy vertically against the inline start or end edge, mirroring in right-to-left text.

compact-placement

compact-placement has a default value of "overlay".

Where the overlay copy goes below the compact width. overlay (the default) keeps it over the image. below moves it into a band under the image, painted in the banner scheme's own surface and ink, for crops that lose their quiet region at narrow widths.

focus

focus has a default value of { x: 0.5, y: 0.5 }.

Normalized focal point used for cover cropping.

ratio

ratio has a default value of "".

Banner target aspect ratio, as a positive number or number / number.

compact-ratio

compact-ratio has a default value of "".

Aspect ratio used below the component-relative compact threshold.

surface-layer

surface-layer has a default value of undefined.

Declares the painted surface depth. Fields use the next local layer, capped at layer 4.

intent

intent has a default value of undefined.

The element's intent — its meaning: neutral (no pin), or success, warning, danger, info — each pinned to its fixed status family (blue for info), retunable per theme via intents. On token-emitting controls a non-neutral intent pins the filled-action pair to that family, derived over the governing zone's theme; class-styled chrome (badges, callouts, status pills) opts out of inline emission and renders its treatment from CSS classes instead. An intent never repaints the surrounding zone, which is what contexts are for. Removing the attribute restores the element's own default intent.

context

context has a default value of undefined.

A theme-defined color zone. The selected context rebinds designer-facing roles and emits a complete, contrast-enforced semantic token table on this host for descendants to inherit.

Methods

<esp-image> has the following methods:

traverseToClosest

Traverse up the DOM tree to find the closest element that matches the selector. This method is aware of shadow DOM boundaries and will traverse through them to find the element.

CSS Parts

<esp-image> has the following CSS parts:

frame

The stable banner/image frame.

image

The stable media-box wrapper in every source mode.

scrim

The decorative legibility layer.

texture

The procedural and consumer-supplied texture layers.

overlay

The positioned semantic overlay region.

CSS Properties

<esp-image> has the following CSS properties:

--esp-image-border

Border; defaults to 2px solid var(--esp-color-border) or none in banner mode.

--esp-image-border-radius

Corner radius; defaults to var(--esp-size-border-radius) or zero in banner mode.

--esp-image-object-position

Final object-position override.

--esp-image-compact-width

Component-width threshold; defaults to 40rem.

--esp-image-scrim-color

Scrim ink; defaults to the theme background when the banner polarity matches the scheme, else a polarity-pinned derivation. A scrim-edge="flush" join is seamless only while the ink is the adjoining band's own color, so an explicit cross-scheme banner-scheme (or an override to any other color) makes the edge a deliberate color break rather than a dissolve.

--esp-image-scrim-opacity

Scrim-strength opacity override.

--esp-image-scrim-edge-depth

How far a scrim-edge="flush" edge takes to relax from full alpha to the ordinary scrim strength, along the gradient axis; defaults to 12%.

--esp-image-scrim-edge-image

Background image painted over the flush edge and faded out with it — the adjoining band's seamless tile; defaults to none. Inert unless scrim-edge="flush" resolves onto a directional scrim.

--esp-image-scrim-edge-size

Background size for --esp-image-scrim-edge-image; defaults to auto. Match the adjoining band's own background-size for a continuous tile.

--esp-image-texture-color

Procedural texture ink; defaults to a theme-derived color following banner-scheme.

--esp-image-texture-opacity

Procedural texture opacity override.

--esp-image-texture-scale

CSS-gradient texture pitch override.

--esp-image-texture-blend-mode

Texture blend-mode override.

--esp-image-texture-image

Consumer-supplied texture background image.

--esp-image-texture-image-opacity

Consumer texture opacity.

--esp-image-texture-repeat

Consumer texture repeat.

--esp-image-texture-position

Consumer texture position.

--esp-image-texture-size

Consumer texture size.

--esp-image-duotone-shadow-color

Duotone shadow stop; defaults to a dark stop derived from the theme primary.

--esp-image-duotone-highlight-color

Duotone highlight stop; defaults to a light stop derived from the theme complementary.

--esp-image-overlay-padding

Overlay content padding.

--esp-image-overlay-measure

Maximum line length of the overlay copy; defaults to 36rem for a copy stack and none for a headline on its own.

--esp-image-overlay-gap

Space between the headline and supporting copy in a copy stack; the action sits twice as far below. Defaults to var(--esp-size-small).

--esp-image-banner-max-block-size

A soft height cap for a banner: the ratio-sized height is the smaller of the ratio height and this length, and copy that would not fit (spacing included) grows the banner past it. In the compact band it caps only the image box. Unlike max-block-size, it never clips copy. Defaults to none.

--esp-image-banner-block-size

A soft height target for a banner: it replaces the ratio as the banner height (viewport-fill banners), and copy that would not fit grows the banner past it. In the compact band it sizes only the image box. Unlike block-size, it never clips copy. Defaults to none.

--esp-image-static-ratio

Read only by the static banner-static.css stylesheet before upgrade: the ratio value, e.g. 3 / 1. A server sets it inline beside the attribute.

--esp-image-static-compact-ratio

Read only by the static banner-static.css stylesheet before upgrade: the compact-ratio value, if any.

--esp-image-overlay-color

Overlay text color; defaults to the theme heading color when the banner polarity matches the scheme.

--esp-image-overlay-text-shadow

Overlay text shadow; defaults to a soft polarity-matched halo. Set to none to disable.

--esp-field-background

Background color of the shared field shell. Defaults to one layer above the nearest declared surface, capped at var(--esp-color-layer-4), or var(--esp-color-layer-2) without a declared surface. An explicit override wins.

--esp-field-border-color

Border color of the shared field shell. Defaults to var(--esp-color-border).

--esp-field-border-width

Border width of the shared field shell. Defaults to 1px.

--esp-field-text-color

Text color used inside the shared field shell. Defaults to var(--esp-color-text).

--esp-field-hover-bg

Hover background color of the shared field shell. Derived from --esp-field-background.

--esp-field-focus-bg

Focus background color of the shared field shell. Derived from --esp-field-background.

--esp-field-focus-shadow

Shadow color used for shared field focus treatment. Defaults to var(--esp-color-shadow).

Components API Guides Getting started Styling Espalier Browser support GitHub npm package Taproot I/O