<div id="example-card" class="cwf-card cwf-card--gray">
<div class="cwf-card__container">
<div class="cwf-card__body">
<h3>Gray card</h3>
<p>This is an example of a gray card. Its background is gray and its borders dark gray to help separate it from surrounding content.</p>
</div>
</div>
</div>
{%- set namespace = 'cwf-card' -%}
{%- set id_value = id ?? namespace -%}
{%- set id_attribute = 'id="' ~ id_value ~ '"' -%}
{%- set attributes = [id_attribute] -%}
{%- set classes = [namespace] -%}
{%- set has_image = image is defined and image != false -%}
{%- if has_image -%}
{% set classes = classes|merge([namespace ~ '--image']) %}
{%- endif -%}
{%- if theme -%}
{% set classes = classes|merge([namespace ~ '--' ~ theme]) %}
{%- endif -%}
{%- if stacked -%}
{% set classes = classes|merge([namespace ~ '--stacked']) %}
{%- endif -%}
{%- if reversed -%}
{% set classes = classes|merge([namespace ~ '--reversed']) %}
{%- endif -%}
{%- if borderless -%}
{% set classes = classes|merge([namespace ~ '--borderless']) %}
{%- endif -%}
{%- if class -%}
{% set classes = classes|merge(class|split(' ')) %}
{%- endif -%}
{%- set class_attribute = 'class="' ~ (classes|join(' ')) ~ '"' -%}
{%- set attributes = attributes|merge([class_attribute]) -%}
{%- if label -%}
{% set attributes = attributes|merge(['data-label="' ~ label ~ '"']) %}
{%- endif -%}
<div {{ attributes|join(' ') }}>
<div class="cwf-card__container">
<div class="cwf-card__body">
{{ content }}
</div>
{%- if has_image -%}
<div class="cwf-card__media">
<img src="{{ image.src }}"
class="cwf-card__background"
role="presentation" />
<img src="{{ image.src }}"
alt="{{ image.alt }}"
class="cwf-card__image" />
</div>
{%- endif -%}
</div>
</div>
{
"id": "example-card",
"content": "<h3>Gray card</h3><p>This is an example of a gray card. Its background is gray and its borders dark gray to help separate it from surrounding content.</p>",
"theme": "gray"
}
// Card component styles
@use "../../shared/media";
@use "../../shared/style";
@use "../../shared/theme";
@use "sass:map";
@use "../../resets/heading/shared" as heading;
@use "../../utilities/marker/shared" as marker;
// Selector prefix
$prefix: "cwf" !default;
// Card colors
$background-color: style.color("white") !default;
$border-color: style.opacity("black", 12.5%) !default;
$color: style.color("gray-dark") !default;
$marker__background-color: style.color("gold") !default;
$link__color: style.color("blue", "accent") !default;
.#{$prefix}-card {
@include style.spacing;
border: 1px solid var(--cwf-card--border-color);
background-color: var(--cwf-card--background-color);
@include style.z-index("content");
--cwf-card--background-color: #{$background-color};
--cwf-card--border-color: #{$border-color};
--cwf-card--foreground-color: #{$color};
--cwf-card--marker-color: #{$marker__background-color};
--cwf-card--link-color: #{$link__color};
&[data-label] {
position: relative;
&:before {
content: attr(data-label);
position: absolute;
top: 0;
left: 0;
display: inline-block;
padding: 0.25rem 0.5rem;
background-color: theme.accent--background();
color: theme.accent--foreground();
@include style.z-index("content", "middle");
}
}
}
.#{$prefix}-card--borderless:not(.#{$prefix}-card--accent, .#{$prefix}-card--gray) {
border: none;
}
// Accented card colors
$background-color--accent: theme.accent--background() !default;
$color--accent: theme.accent--foreground() !default;
$link__color--accent: theme.accent--foreground() !default;
// Gray card colors
$background-color--gray: style.darken("white-dark", 5.25%) !default; // #ebebeb
$color--gray: style.color("gray-dark") !default;
$link__color--gray: style.color("blue", "accent") !default;
// Card themed button colors
$button--themed__background-color: style.color("white") !default;
$button--themed__border-color: style.color("gray-lightest") !default;
$button--themed__color: style.color("blue", "accent") !default;
$button--themed__background-color--active: style.lighten(
style.color("blue", "accent"),
17%
) !default;
$button--themed__color--active: style.color("gray-dark") !default;
$button--themed__background-color--interact: style.color("gold") !default;
$button--themed__color--interact: style.color("gray-dark") !default;
$button--themed: (
"background-color": $button--themed__background-color,
"border-color": $button--themed__border-color,
"color": $button--themed__color,
"active-background-color": $button--themed__background-color--active,
"active-color": $button--themed__color--active,
"hover-focus-background-color": $button--themed__background-color--interact,
"hover-focus-color": $button--themed__color--interact
);
$button--themed--keys: map.keys($button--themed);
// Card themes
$themes: (
"accent": (
"background-color": $background-color--accent,
"foreground-color": $color--accent,
"link-color": $link__color--accent,
"button": $button--themed
),
"gray": (
"background-color": $background-color--gray,
"foreground-color": $color--gray,
"link-color": $link__color--gray,
"button": $button--themed
)
);
$theme--keys: "background-color", "foreground-color", "link-color", "button",
"theme";
@function theme--validate($instructions) {
@return map.keys($instructions) == $theme--keys;
}
@function button--themed--validate($instructions) {
@return map.keys($instructions) == $button--themed--keys;
}
@mixin theme($instructions) {
@if theme--validate($instructions) {
$theme: map.get($instructions, "theme");
$background-color: map.get($instructions, "background-color");
$foreground-color: map.get($instructions, "foreground-color");
$link-color: map.get($instructions, "link-color");
$button: map.get($instructions, "button");
.#{$prefix}-card--#{$theme} {
--cwf-card--background-color: #{$background-color};
--cwf-card--foreground-color: #{$foreground-color};
--cwf-card--link-color: #{$link-color};
@if $button and button--themed--validate($button) {
$button-background-color: map.get($button, "background-color");
$button-border-color: map.get($button, "border-color");
$button-color: map.get($button, "color");
$button-active-background-color: map.get(
$button,
"active-background-color"
);
$button-active-color: map.get($button, "active-color");
$button-hover-focus-background-color: map.get(
$button,
"hover-focus-background-color"
);
$button-hover-focus-color: map.get(
$button,
"hover-focus-color"
);
& .#{$prefix}-card__body {
.t4_button,
.#{$prefix}-button {
--cwf-button--background-color: #{$button-background-color};
--cwf-button--border-color: #{$button-border-color};
--cwf-button--color: #{$button-color};
--cwf-button--active--background-color: #{$button-active-background-color};
--cwf-button--active--color: #{$button-active-color};
--cwf-button--hover-focus--background-color: #{$button-hover-focus-background-color};
--cwf-button--hover-focus--color: #{$button-hover-focus-color};
}
}
} @else {
@warn "Invalid card theme button instructions provided!";
}
@if $theme == "accent" {
&[data-label]:before {
background-color: var(--cwf-card--marker-color);
color: initial;
}
}
}
} @else {
@warn "Invalid card theme instructions provided!";
}
}
@mixin theme--official($theme) {
$colors: map.get($themes, $theme);
$instructions: map.merge(
$colors,
(
"theme": $theme
)
);
@include theme($instructions);
}
@each $theme, $colors in $themes {
@include theme--official($theme);
}
// Container
@mixin stacked {
.#{$prefix}-card--stacked:is(.#{$prefix}-card--image) &,
.#{$prefix}-grid > .#{$prefix}-card:is(.#{$prefix}-card--image) &,
.#{$prefix}-grid
> .#{$prefix}-carousel
.#{$prefix}-card:is(.#{$prefix}-card--image)
& {
@content;
}
}
@mixin reversed {
.#{$prefix}-card--reversed:is(.#{$prefix}-card--image) & {
@content;
}
}
@mixin borderless {
.#{$prefix}-card--borderless:not(.#{$prefix}-card--accent, .#{$prefix}-card--gray)
& {
@content;
}
}
@mixin borderless--image {
.#{$prefix}-card--borderless:not(.#{$prefix}-card--accent, .#{$prefix}-card--gray):is(.#{$prefix}-card--image)
& {
@content;
}
}
@mixin borderless--reversed {
.#{$prefix}-card--borderless:not(.#{$prefix}-card--accent, .#{$prefix}-card--gray):is(.#{$prefix}-card--image):is(.#{$prefix}-card--reversed)
& {
@content;
}
}
@mixin borderless--stacked {
.#{$prefix}-card--borderless:not(.#{$prefix}-card--accent, .#{$prefix}-card--gray):is(.#{$prefix}-card--image):is(.#{$prefix}-card--stacked)
&,
.#{$prefix}-grid
> .#{$prefix}-card--borderless:not(.#{$prefix}-card--accent, .#{$prefix}-card--gray):is(.#{$prefix}-card--image)
& {
@content;
}
}
@mixin labeled {
.#{$prefix}-card[data-label] & {
@content;
}
}
@mixin labeled--image {
.#{$prefix}-card[data-label]:is(.#{$prefix}-card--image) & {
@content;
}
}
@mixin labeled--image--stacked-or-reversed {
.#{$prefix}-card[data-label]:is(.#{$prefix}-card--image):is(.#{$prefix}-card--stacked, .#{$prefix}-card--reversed)
&,
.#{$prefix}-grid
> .#{$prefix}-card[data-label]:is(.#{$prefix}-card--image)
& {
@content;
}
}
.#{$prefix}-card__container {
display: flex;
flex-direction: column-reverse;
@include theme.contain;
@include media.breakpoint {
flex-direction: row;
@include reversed {
flex-direction: row-reverse;
}
@include stacked {
flex-direction: column-reverse;
}
}
}
// Media
.#{$prefix}-card__media {
position: relative;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
background-color: style.color("black");
overflow: hidden;
@include media.breakpoint {
width: 25%;
@include stacked {
width: 100%;
}
}
}
.#{$prefix}-card__background {
display: block;
position: absolute;
top: 50%;
transform: translateY(-50%) scale(2);
filter: blur(2rem);
backface-visibility: hidden;
opacity: 1;
}
.#{$prefix}-card__image {
position: relative;
}
// Body
@mixin accented {
.cwf-card--accent & {
@content;
}
}
.#{$prefix}-card__body {
flex: 1;
padding: 1.5rem;
color: var(--cwf-card--foreground-color);
@include style.children;
a {
font-weight: 700;
--cwf-link--color: var(--cwf-card--link-color);
--cwf-link--active--color: var(--cwf-card--link-color);
}
@include heading.reduce;
@include borderless {
padding: 1.5rem 0;
}
@include borderless--image {
@include media.breakpoint {
padding-right: 1.5rem;
}
}
@include borderless--reversed {
@include media.breakpoint {
padding-right: 0;
padding-left: 1.5rem;
}
}
@include borderless--stacked {
padding: 1.5rem 0;
}
@include labeled {
margin-top: 2rem;
}
@include labeled--image {
margin-top: 0;
@include media.breakpoint {
margin-top: 2rem;
}
}
@include labeled--image--stacked-or-reversed {
margin-top: 0;
}
}
The card component should be used to highlight key elements on the page that you want to visually distinguish from the rest of your content. While any WYSIWYG content can be added inside this component, it is recommended to keep the content in this element concise to ensure the integrity and original intent of the component.
By default, a card will be styled with a white background and a darkened border. If an image is used, it will appear next to the body content on viewports larger than a standard mobile device. These defaults can be overridden with the following modifier classes.
.cwf-card--image
- Indicates a card has an image and allows other image-dependent modifiers to work properly. This layout is automatically applied when an image is used..cwf-card--stacked
- Stacks a card’s image above its body content. This layout is automatically applied when the card is within a grid component..cwf-card--reversed
- Reverses a card’s image and body order, showing the image to the left of the body instead of the right..cwf-card--borderless
- Removes a card’s border and reduces its internal padding. This only works on non-themed cards..cwf-card--accent
- Alters the card’s style to use the theme’s accent color for the background (blue by default)..cwf-card--gray
- Alters the card’s style to have a gray background.By adding a data-label="$"
attribute to a card, where $
is the label message, a prominent label will be added to the top-left of the card to better highlight important content. Labels should be short/succinct, as lengthy labels will most likely break card styles in certain instances.
Cards are implemented in T4 as the “Card” plugin, meaning its classes are .plugin-
prefixed instead of .cwf-
prefixed.
When using the card content type in the sidebar/sub-nav area of a T4 website, the stacked layout and accent theme will automatically be applied, rendering the card with its image (if provided) above its body content and with an accent background color (blue by default).
In the “Name” field of the “Card” plugin, the following case-insensitive keywords can be used to change the area in which the card appears:
Feature
- Moves the card to the feature area of the page (below the main navigation, above the sidebars and main content).Sidebar
- Moves the card to the sidebar area of the page (right of the main content).Footer
- Moves the card to the footer area of the page (above the footer, below the sidebars and main content).This plugin can also be used within the global “Site-Feature”, “Site-Sidebar”, and “Site-Footer” sections to have it displayed globally within the feature, sidebar, and footer areas respectively.
In the “Name” field of the “Card” plugin, the following injectors can be used:
id:{custom_id}
- Overrides the default, T4 ID of the card with a custom ID.theme:{accent|gray,grey}
- Sets the theme of the card.stacked:{true|false}
- Enables/disables the stacked layout of the card.reversed:{true}
- Enables the reversed layout of the card.borderless:{true}
- Enables the borderless layout of the card.label:{label_text}
- Adds a label to the card.class:{custom_classes}
- Adds custom classes to the card.style:{custom_styles}
- Adds custom styles to a style
attribute of the card.before:{custom_html}
- Adds custom HTML before the card.after:{custom_html}
- Adds custom HTML after the card.