Cover
Every example on this page is live, drawn by the library's Storybook, where you can also change its props and see the result.
A picture in a fixed frame: a film's poster, a podcast's artwork, a dish's photo. The picture is cropped to fill the frame, never squashed, and shown in greys with a little extra contrast, which is how an e-ink frame shows it, so its shapes stay clear on the display.
When there is no picture, or it fails to load, the frame is outlined with a glyph in the middle instead, so a missing picture looks the same in every widget.
Import
import { Cover } from "~lib/data-display/cover";
Props
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
src | string | null | No | N/A | The picture's address. Leave it out, or pass null, when there is none |
alt | string | No | "" | What the picture shows, for screen readers. Leave it out when text beside it says it |
width | number | string | No | N/A | Width in pixels, or any CSS width such as "100%" |
height | number | string | "fill" | No | N/A | Height in pixels, any CSS height, or "fill" for the room its column leaves |
aspect | number | No | 1 | Width over height, used for the edge you leave out, such as 2 / 3 for a poster |
radius | "none" | "sm" | "md" | "lg" | "full" | No | "md" | How rounded the corners are; "full" makes a circle of a square frame |
fallback | IconName | No | "image" | The Icon shown when there is no picture |
fit | "cover" | "contain" | No | "cover" | Crop the picture to fill the frame, or show all of it at its own proportion |
mark | IconName | No | N/A | An Icon in the corner saying what the picture is |
tone | "picture" | "ink" | No | "picture" | Show the picture as it is, or "ink" to draw only its shape in ink |
invert | boolean | "light" | "dark" | No | false | Draw the picture as its negative: on every frame, or only on a light or only on a dark one |
rotate | 0 | 90 | 180 | 270 | No | 0 | Turn the picture, its placeholder and its mark clockwise inside the frame |
Give width and height for an exact box, or one of them and aspect. With only one edge and no
aspect the frame is square. With neither edge it is as wide as its parent. To let the picture
take whatever height is left, see Filling the Room Left.
Examples
Default
Aspects
The same picture in a poster, a square and a wide frame. Each one is cropped to fill its frame.
Radius
Fallback
With no picture, or one that fails to load, the frame shows its fallback glyph. Pick one that
says what is missing, such as "utensils" for a recipe, "film" for a film or "book" for a
book.
Marks
mark puts a small glyph in the top right corner saying what the picture is, the way a social app
marks a post: "film" for a video, "clone" for an album of several pictures. It sits on a disc
so it stays clear over any picture, and grows and shrinks with the frame.
<Cover
src={post.thumbnailUrl}
width={96}
mark={post.isVideo ? "film" : post.isAlbum ? "clone" : undefined}
/>
Screen readers skip the mark, so if it matters, say it in alt too.
In Ink
Some pictures are drawn in one color with see-through parts, such as an emblem, a logo or a
silhouette. Shown as they are, a dark one disappears on a dark frame. Set tone="ink" and the
cover draws only the picture's shape, in the ink of whichever theme the frame uses, so it reads on
a light frame and a dark one alike. fit, radius, mark and the fallback glyph work the same
as for any picture.
<Cover
src={sign.emblemUrl}
alt={sign.name}
width={72}
radius="full"
tone="ink"
/>
Inverted
Some pictures have the opposite ground to the frame's: a chart of the night sky is light stars on
a dark sky, which on a light frame is a dark block. invert draws the picture as its negative.
invert="light" inverts it on a light frame only, so the sky takes the frame's paper and the
stars read as ink, and leaves it as it is on a dark frame, where it already reads that way.
invert="dark" does the same the other way round, for a dark drawing on a light ground shown on a
dark frame, and invert alone inverts it on both. The cover follows the frame's theme by itself,
in a saved preview as much as on the frame. A picture with tone="ink" already follows the theme,
so invert leaves it alone.
<Cover src={skyChart.imageUrl} fit="contain" height="fill" invert="light" />
Turned
rotate turns the picture inside its frame by a quarter, a half or three quarters clockwise,
with its placeholder and its mark, the way a reversed tarot card lies upside down. The frame keeps
the size and proportion you give it, so a quarter turn crops the picture to the frame on its side,
and a whole picture (fit="contain") takes the proportion of the picture once turned.
<Cover
src={card.imageUrl}
alt={card.name}
width={84}
aspect={7 / 12}
rotate={card.reversed ? 180 : 0}
/>
The Whole Picture
A painting's edges are part of it, so don't crop it: set fit="contain". width and height
then name the box the picture has to fit in, such as "100%" of the room your layout leaves, and
the frame takes the picture's own proportion inside it, as large as fits, centered. You never
need the picture's size from the API.
<div className="min-h-0 flex-1">
<Cover src={artwork.imageUrl} fit="contain" width="100%" height="100%" />
</div>
Filling the Room Left
In a flex column, height="fill" lets the picture take the height the rest of the column leaves.
It grows until it spans the column at its aspect, and in a shorter box it shrinks before the
text does, staying centered, so your title and footer always fit. You don't need to pick a size
for each box.
<div className="flex h-full flex-col gap-2">
<Cover src={post.imageUrl} height="fill" aspect={4 / 3} />
<Paragraph size="sm" weight="semibold" lines={2}>
{post.title}
</Paragraph>
<div className="mt-auto">
<Paragraph size="xs">{post.date}</Paragraph>
</div>
</div>
Give width too and the picture never grows wider than that; the column's own alignment, such as
items-center, places it. With fit="contain" the whole picture grows the same way at its own
proportion, and the placeholder and mark fill the same room.
In a row with a height of its own, such as one with min-h-0 flex-1 in its column, the picture
takes the row's height instead and is as wide as its aspect makes it.
<div className="flex min-h-0 flex-1 items-center gap-3">
<Cover src={film.posterUrl} height="fill" aspect={2 / 3} fallback="film" />
<div className="min-w-0 flex-1">
<Heading level={3}>{film.title}</Heading>
</div>
</div>
In a List
A row as tall as its cover, with the title and the show beside it. An episode without artwork gets the placeholder, so the rows still line up.
import { Cover } from "~lib/data-display/cover";
import { Paragraph } from "~lib/typography";
const Episode = ({
title,
show,
artwork,
}: {
title: string;
show: string;
artwork?: string;
}) => (
<div className="flex items-center gap-3">
<Cover src={artwork} width={56} fallback="film" />
<div className="min-w-0 flex-1">
<Paragraph size="sm" weight="semibold" lines={1}>
{title}
</Paragraph>
<Paragraph size="xs">{show}</Paragraph>
</div>
</div>
);