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:
- Project a light-DOM
<picture>— the recommended form for published / server-rendered markup. Provide a real<picture>(or a bare<img>) as a child andesp-imageprojects 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. - Declare esp-image-option children —
the editor / fallback form.
esp-imagebuilds a shadow<picture>andsrcsetfrom them. Uselocal-imagewhen 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).