Repository navigation
[6.x] Cover cards for elements with asset thumbnails - #20148
Conversation
Asset cards for images that can fill a card are covered by the image, with the header and content laid over it in white or black. The card picks the ink and the least bar, backing, and scrim opacity that keeps it legible from the image's luminance. Co-authored-by: Amp <amp@ampcode.com> Amp-Thread-ID: https://ampcode.com/threads/T-c27fac24-ac8b-4256-be4b-7c1306ada663
📚 Storybook previews@craftcms/ui — open Storybook Changed components: resources/js — open Storybook No changed components detected in this Storybook. |
Amp-Thread-ID: https://ampcode.com/threads/T-c27fac24-ac8b-4256-be4b-7c1306ada663 Co-authored-by: Amp <amp@ampcode.com>
…lumn Backed header controls get the label bar's blur and a 65% floor, and the image blurs in by 1px under the body's scrim, which now aims for 5:1 with a 40% floor. Cover cards no longer grow past their grid column when a long title would give their aspect-ratio box a content-based minimum width. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
A cover card's border took the card's data-color (an entry type's color, for instance), so it's now always neutral, as the image stands in for the color. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
The body sits inside the card's 1px border, but the image reaches the card's edges, so a band of the sharp image showed under the translucent border along the sides and bottom, and around the bottom corners. The blur layer now reaches the card's edges, with its corners rounded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
Card view designers get a Thumb Style setting, with Normal and Cover options illustrated like Matrix's View Mode setting. It defaults to Cover for asset volumes, and is shown for other element types once a relation field is chosen as the thumb source. The designer's preview card adapts to the selection, and Thumb Alignment is hidden while cards are covered. Thumbnail Source is renamed to Thumb Source. Co-authored-by: Amp <amp@ampcode.com> Amp-Thread-ID: https://ampcode.com/threads/T-c27fac24-ac8b-4256-be4b-7c1306ada663
The card view designer's preview now shows a photo of Mt. Bachelor (by Bonnie Moreland, public domain) as both the cover image and the normal thumbnail, and in the sample chip, in place of the image icon. Thumb Alignment now follows Thumb Style, since it depends on that selection. Co-authored-by: Amp <amp@ampcode.com> Amp-Thread-ID: https://ampcode.com/threads/T-c27fac24-ac8b-4256-be4b-7c1306ada663
Co-authored-by: Amp <amp@ampcode.com> Amp-Thread-ID: https://ampcode.com/threads/T-c27fac24-ac8b-4256-be4b-7c1306ada663
…e grid A cover card's 4:3 aspect ratio couldn't grow, as the card clips its overflow, so content taller than that ran past its bottom padding. The card is now a grid with a 4:3 spacer under its content, so it's at least 4:3 and grows when it needs to. Card view designers round the preview card's height up to a whole number of grid cells, plus its border, so both of its edges land on a row of dots, as the chip's do. Also drops the note about images too small to cover a card from the Thumb Style instructions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
Beside the preview, the settings column scrolls rather than stretching the designer taller than the preview needs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
A card header without a label now has no fill or rule of its own, so its checkbox and action button float over the card, as a cover card's do, in the same place they'd be with a label bar. A header with nothing to show isn't rendered at all, so the body is flush with the top of the card. Matrix blocks' and workflow stages' titles are marked as card labels, so they keep their bars. Card view designer previews only get a header for entry types, which show their entry type's label and icon there. Co-authored-by: Amp <amp@ampcode.com> Amp-Thread-ID: https://ampcode.com/threads/T-c27fac24-ac8b-4256-be4b-7c1306ada663
Co-authored-by: Amp <amp@ampcode.com> Amp-Thread-ID: https://ampcode.com/threads/T-c27fac24-ac8b-4256-be4b-7c1306ada663
…irline Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
A cover card's border takes its data-color again, in the page's theme, as a normal card's border would. What's laid over the image keeps the white or black ink the image calls for. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
Vue sets a custom element's attributes as its properties, as strings, so a card view designer that loaded with Thumb Style set to Cover gave its preview card its luminance as JSON text. The card treated that as unknown luminance, and covered the image with its heaviest bar and scrim. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
…e stars in dark mode The card view designer's sample image now follows the theme being shown, with each image's own luminance, and its preview is rendered again when the theme changes without a page load. The images are JPEGs without metadata. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
A re-rendered preview card showed at its natural height for a frame before it was snapped to the grid, so a cover card's content jumped a few pixels whenever the preview changed, as it does when the theme switches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
Its thumbnail is the Cover illustration's mountain-and-sun photo, without the scrim and text, at the 4:3 shape the preview's thumbnail takes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
… image from causing a contrast failure
…story versus an edge case, prevent edge cases from showing up in published docs
…e/cover-cards-a11y # Conflicts: # packages/craftcms-ui/src/components/card/Card.mdx # packages/craftcms-ui/src/components/card/card.styles.ts # packages/craftcms-ui/src/components/card/card.ts Co-authored-by: gcamacho079 <17525979+gcamacho079@users.noreply.github.com>
|
@brandonkelly This is awesome, and I expect it will look great on photo-driven sources! One reservation is the flip between white and dark text (with the matching scrim) depending on image luminance. With both variants side by side in the same grid, it looks a little inconsistent to me, and I find the dark scrim with light text more legible in most cases. In the example below, the dark text would be applied (left), but the white text version (right) looks cleaner to me. The white scrim washes the photo out, while the dark one reads more like a natural shadow and keeps the image's depth:
I realize white text on very bright images needs a heavier scrim to hold contrast, so there's a tradeoff. Maybe the flip to dark text could kick in only for the brightest images, so most cards stay consistent? |
Field layouts can now have multiple, ordered thumb sources, and chips and cards show the thumbnail from the first one that has one. Field layouts store `thumbSources` rather than `thumbFieldKey`, which is kept as a deprecated get/set hook onto the first source. See #20156 Co-authored-by: Amp <amp@ampcode.com> Amp-Thread-ID: https://ampcode.com/threads/T-c27fac24-ac8b-4256-be4b-7c1306ada663
Co-authored-by: Amp <amp@ampcode.com> Amp-Thread-ID: https://ampcode.com/threads/T-c27fac24-ac8b-4256-be4b-7c1306ada663
…where - Added CraftCms\Cms\Element\Data\Thumb, which renders at any size and may carry the asset its image comes from. ElementInterface::getThumb() returns the first thumb source's thumbnail, or else the element's own, and getThumbHtml() renders it. Cards resolve the thumbnail once for both their thumbnail and cover. - Thumb Style is now available for every field layout. User photos and assets from relation fields within Content Blocks can cover cards. - Fixed a PHPStan error in HasThumbnails. Co-authored-by: Amp <amp@ampcode.com> Amp-Thread-ID: https://ampcode.com/threads/T-c27fac24-ac8b-4256-be4b-7c1306ada663
Co-authored-by: Amp <amp@ampcode.com> Amp-Thread-ID: https://ampcode.com/threads/T-c27fac24-ac8b-4256-be4b-7c1306ada663
[6.x] Thumb Sources
[6.x] Cover cards: fixed fills so text and controls pass contrast over any image
- Cover cards use white on black unless the darkest part of every region behind the content (or header) has a relative luminance of at least 0.25, as suggested in #20148. Both themes' fills pass over any image, so this only changes which one shows. - Card attribute previews on cover cards are limited to one line. - Updated the CoverLongContent story now that cover cards grow. Co-authored-by: Amp <amp@ampcode.com> Amp-Thread-ID: https://ampcode.com/threads/T-c27fac24-ac8b-4256-be4b-7c1306ada663
|
@thupsi Good call, done:
|


Description
Card view designers get a Thumb Style setting, with Normal and Cover options. With Cover, element cards whose thumbnail is an image that can fill a card become cover cards: the image covers the whole card, and the header and content are laid over it. The card is passed the image's luminance (
analysis.colors.luminance) and works out everything else itself.This applies to any element type whose thumbnail is an asset's image:
The theme rule prefers white on black, as a black scrim reads as a natural shadow, while a white one washes out the photo (thupsi's suggestion). It only switches to black on white once the image is light throughout, where the black scrim would look like a gray smear. The cutoff was picked from a brightness ladder of the same photo. Since both themes' fills pass over any pixel, the cutoff only affects looks, not contrast.
Thumb Style setting
Cover cards are opt-in per field layout, through a Thumb Style setting in the card view designer's Appearance tab:
resources/public/images/thumb-styles/{normal,cover}.svg, with dark-mode variants).cardThumbStylein the field layout config, withFieldLayout::getCardThumbStyle(),setCardThumbStyle(), and the fluentcardThumbStyle().resources/public/images/thumb-styles/bachelor-{light,dark}.jpg, 800×600 JPEGs without metadata), chosen for the theme being shown, and rendered again if the theme changes without a page load. The sample chip shows it as its thumbnail too. Each image's luminance, measured withImages::sample(), is passed to the cover card, so it gets the same treatment a real asset with that image would. Both get white on black.CardViewDesigner::snapPreviewCard()).Variations
All of these are in the Card stories (
Cover*), and documented inCard.mdx. Hard inputs and failure states are inComponents/Card/Edge Cases(#20152), which share their fixtures with the browser tests.Cover): when the header has text (thelabelattribute or slot, a[data-card-label]element in theheaderslot, which element cards' titles now carry, or a badge), the header gets a bar the height of a normal card's header. It's 60% black or 52% white, blurred with the same blur as the index footer (--blur-md), and has a 1px bottom border in translucent white (20%) or black (15%).CoverBackedControls): with no text in the header, the checkbox and plain buttons always get a 65% backing in the color opposite their ink, blurred like the label bar. It strengthens to 80% when a button is hovered or pressed. The checkbox's backing extends 1px past its border, drawn with a0 0 0 1pxbox shadow.backdrop-filterlayer faded in with a mask). The blur reaches the card's edges, under its 1px border, as the image does, so no band of the sharp image shows along the sides and bottom.CoverHardImages): plain white and black, thin stripes, and portraits whose measured rows are cropped out.CoverWithoutLuminance): images sampled before luminance was don't have it yet, so the card uses white on black.CoverImageNotLoaded,CoverBrokenImage): the fills don't depend on the image, so text is legible before it loads. If it fails to load, the card falls back to a normal one with its thumbnail.CoverLongContent,CoverManyAttributes): the card grows past 4:3 to fit its content, with the scrim behind all of it, and the rest of its row grows to match.CoverActive): an accent border and a 2px accent ring.CoverAlongsideNormalCards): only thumbnail images at least 480×360, between 3:4 and 2:1, and not possibly transparent get a cover (Asset::canCoverCard()). Smaller, narrower, or wider ones would be scaled up or cropped too far, and transparent ones would show the card's fill through them, so they keep the normal thumbnail card.The layout rules, which hold for every variation:
data-themegoes on the slotted header and content, not on the card, so checkboxes and other controls take the theme that matches the ink, while the card's own border, corners, and shadow follow the page's theme.data-color(such as an entry type's color) only colors its outer border, semi-transparent over the image, in the page's light or dark theme as a normal card's border would be. Everything laid over the image keeps its white or black ink.aspect-ratiofrom growing, so it's a grid with a 4:3 spacer under its content instead.::after), and the card clips to its rounded corners.Normal card headers
Normal cards follow the same header rules as cover cards:
labelattribute or slot, or a[data-card-label]element in theheaderslot), the header keeps its bar. Element cards' card titles carrydata-card-label, so entry cards keep theirs, with their entry type's label and icon.card--bare-header). Its border stays, transparent, so the controls are exactly where they'd be with a bar. In a selected card, its controls take the body's colors rather than the selected bar's, which would be light on light.divs andspans), the header isn't rendered, so the body is flush with the top of the card. The card re-renders on text changes in its light DOM too, as element cards fill their headers after it first renders.data-card-label, so they keep their bars.Merged into this branch
ElementInterface::getThumb(), which returns a size-independentCraftCms\Cms\Element\Data\ThumbwithgetHtml(),getAsset(), andsupportsCover().FieldLayout::$thumbFieldKeyandthumbFieldKey()are deprecated in favor of$thumbSourcesandthumbSources(). See that PR for the full API.Accessibility
Canvasbackground over the image, and the card gets a realCanvasTextborder (Highlightwhen selected). This hasn't been tested in an actual forced-colors browser.<img alt="">, because the card's content already names the element. The thumbnail isn't rendered. The designer's preview photo is decorative too.alt="") and sit in<label for>elements, so clicking one selects its option. Thumb Sources is acraft-checkbox-select, with labeled remove and reorder buttons and an “Add item” menu.color-contrastfor content insidecraft-card[cover-src]only, and the browser tests check that contrast instead.card.a11y.mdhas a Cover cards section, and each requirement names the test that verifies it.The screenshots below predate #20152's fixed fills and the dark-first theme rule, so some bars and scrims in them are lighter, and On White II would now get white on black.
Screenshots
The collection folder in cards view. Every image but Wheatfield under Thunderclouds gets a cover.
Checkbox only: the checkbox is backed, since the image behind it is too busy for 4.5:1 on its own, and the backing is blurred.
Wheatfield under Thunderclouds (2400×1173) is wider than 2:1, so it keeps the normal card. Nothing in this folder is under 480×360.
High contrast: Klimt’s The Tree of Life, with regions from bright gold to near black. The blurred scrim keeps the title legible over the busy swirls.
Low contrast: Klimt’s Portrait of Adele Bloch-Bauer I, mostly even midtones.
Light image: Kandinsky’s On White II, the lightest in the folder.
Dark image: Van Gogh’s Self-Portrait, the darkest in the folder.
An entry: The Starry Night, a Collection entry (Work type) whose thumbnail field is an Assets field. Its card title gives the header a label, so it gets the label bar, with the checkbox in it. The Work entry type is violet. This shot predates cover cards taking that color for their border; the Cover designer shot below shows it.
Card view designer
Thumb Style set to Normal, with Thumb Alignment after it. The settings scroll beside the preview (shown scrolled to the bottom).
Thumb Style set to Cover: the preview becomes a cover card, taller than 4:3 to fit its content, with the Exhibit type's indigo as its border, and Thumb Alignment is hidden.
Evidence
cover.test.ts:uses the fixed fills over …: light, dark, busy, pure white, pure black, unknown, and transparent luminance.picks dark, with deep shadows under bright highlightsandpicks dark, when one region has shadowsfail under the old contrast-based rule.picks light, when every region is lightandpicks light, for a pale imagecover the other side of the 0.25 cutoff.reads the header from the top row and the content from the bottom row, and white on black when the luminance is unknown or transparent.card.browser.test.ts, cover cards:gives every pixel behind a cover card’s text 4.5:1, and behind its controls 3:1andgives every pixel behind a cover card’s attributes 4.5:1, over each hard image and an image that hasn't loaded.keeps every line of a cover card’s text and its controls where its fill is at full strength, across widths, text lengths, and text spacing.keeps a cover card’s header controls in place when its header gets a label bar: header, checkbox, label, and button rects are identical with and without the bar.grows a cover card past 4:3 when its content needs more room, andkeeps a cover card within its width when its content has a long title.gives a cover card’s border its color, and nothing else,reads a cover card’s luminance when it’s set as a JSON string,backs a cover card’s plain buttons more strongly when they’re hovered,gives a cover card’s focused … a black and white ring inside the card, andfalls back to a normal card when its cover image fails to load.card.browser.test.ts, normal card headers:drops a card’s header bar when it has no label, keeping its controls in place,keeps a selected card’s bare header controls legible against its body, andleaves out a header with nothing to show, so the body starts at the card’s top.ElementHtmlTest:covers an asset’s card only with an opaque image that can fill it: the size and ratio edges (479/480 wide, 359/360 tall, 2:1 vs. wider, 3:4 vs. narrower) and transparency.CardDesignerTest: the preview is a cover card for asset volumes by default and for layouts set to Cover. Thumb Style is shown for every layout, and Thumb Alignment unless there's no thumbnail or cards are covered. Only entry type previews have a header. The sample image follows the theme.FluentBuilderTest:cardThumbStyledefaults, config round-trip, and rejecting invalid values.thumbFieldKeymaps ontothumbSources.vp test run src/components/card): 134 passed.vue-tsc: clean.🤖 Generated with Claude Code