diff --git a/packages/web/src/scss/components/Card/README.md b/packages/web/src/scss/components/Card/README.md
index 355144d523..fb1f833041 100644
--- a/packages/web/src/scss/components/Card/README.md
+++ b/packages/web/src/scss/components/Card/README.md
@@ -69,6 +69,17 @@ Card can be displayed in a vertical, horizontal, or reversed horizontal layout.
👉 Keep in mind that, no matter the layout, the Card subcomponents must be arranged in the order
[specified above](#card-1).
+### Responsive Card Layout
+
+Card layout can be adjusted based on the breakpoint. To create a responsive layout, use the `tablet` and `desktop`
+infixes, e.g. `Card--tablet--horizontal` or `Card--desktop--vertical`.
+
+```html
+
+
+
+```
+
### Boxed Cards
Card can be displayed with a border and a box shadow on hover.
diff --git a/packages/web/src/scss/components/Card/_Card.scss b/packages/web/src/scss/components/Card/_Card.scss
index 08f6101a47..42eb36ec74 100644
--- a/packages/web/src/scss/components/Card/_Card.scss
+++ b/packages/web/src/scss/components/Card/_Card.scss
@@ -1,67 +1,42 @@
// 1. Get ready for card link overlay.
-// 2. If there is a expanded CardMedia in a boxed vertical card, replace card padding with empty grid columns/rows.
-// 3. Allow shrinking in grid layouts.
-// 4. Make sure links and buttons are clickable.
-// 5. Make text content selectable when there is a stretched CardLink.
-
+// 2. Allow shrinking in grid layouts.
+// 3. Make sure links and buttons are clickable.
+// 4. Make text content selectable when there is a stretched CardLink.
+// 5. Prepare card layouts for different breakpoints.
+// a) Layouts are common for all breakpoints, so they are defined once in the theme file.
+// b) Layout settings are applied to the grid via custom properties.
+// c) If there is an expanded CardMedia in a boxed card, replace card padding with empty grid columns/rows.
+// d) Padding is adjusted for different breakpoints.
+
+@use 'sass:map';
@use '@tokens' as tokens;
@use '../../tools/breakpoint';
@use '../../tools/typography';
@use 'theme';
.Card {
- --#{tokens.$css-variable-prefix}card-padding: #{theme.$padding};
-
@include typography.generate(theme.$typography);
position: relative; // 1.
display: grid;
- min-width: 0; // 3.
+ grid-template-columns: var(--#{tokens.$css-variable-prefix}card-columns); // 5.b
+ grid-template-rows: var(--#{tokens.$css-variable-prefix}card-rows); // 5.b
+ grid-template-areas: var(--#{tokens.$css-variable-prefix}card-areas); // 5.b
+ column-gap: var(--#{tokens.$css-variable-prefix}card-column-gap); // 5.b
+ min-width: 0; // 2.
color: theme.$color;
-
- @include breakpoint.up(tokens.$breakpoint-tablet) {
- --#{tokens.$css-variable-prefix}card-padding: #{theme.$padding-tablet};
- }
}
-// 4., 5.
+// 3., 4.
.Card:has(.CardLink) :where(a:not(.CardLink), button),
.Card:has(.CardLink):has(.CardBody--selectable) :where(p, ul, ol, dl) {
position: relative;
z-index: 1;
}
-.Card--vertical {
- grid-template-rows: auto 1fr auto;
- grid-template-areas:
- 'media'
- 'body'
- 'footer';
-}
-
-.Card--horizontal {
- grid-template-columns: auto 1fr;
- grid-template-rows: auto 1fr auto;
- grid-template-areas:
- 'media logo'
- 'media body'
- 'media footer';
-}
-
-.Card--horizontalReversed {
- grid-template-columns: 1fr auto;
- grid-template-rows: auto 1fr auto;
- grid-template-areas:
- 'logo media'
- 'body media'
- 'footer media';
-}
-
-:is(.Card--horizontal, .Card--horizontalReversed):has(.CardArtwork:not(:only-child), .CardMedia:not(:only-child)) {
- column-gap: theme.$gap;
-}
-
.Card--boxed {
+ --#{tokens.$css-variable-prefix}card-padding: #{theme.$padding}; // 5.d
+
border: theme.$border-width theme.$border-style theme.$border-color;
border-radius: theme.$border-radius;
background-color: theme.$background-color;
@@ -76,77 +51,97 @@
}
}
- &:not(:has(.CardMedia--expanded)) {
- padding: var(--#{tokens.$css-variable-prefix}card-padding);
+ &:has(.CardMedia--expanded) {
+ padding: var(--#{tokens.$css-variable-prefix}card-padding-shorthand); // 5.b
}
-}
-.Card--boxed.Card--vertical {
- &:has(.CardMedia--expanded) {
- grid-template-columns:
- var(--#{tokens.$css-variable-prefix}card-padding)
- 1fr
- var(--#{tokens.$css-variable-prefix}card-padding); // 2.
-
- grid-template-rows:
- var(--#{tokens.$css-variable-prefix}card-padding)
- auto
- 1fr
- auto;
- grid-template-areas:
- 'media media media'
- 'media media media'
- '. body .'
- '. footer .';
+ &:not(:has(.CardMedia--expanded)) {
+ padding: var(--#{tokens.$css-variable-prefix}card-padding);
}
- &:has(.CardMedia:not(:only-child)) {
- padding-bottom: var(--#{tokens.$css-variable-prefix}card-padding);
+ @include breakpoint.up(tokens.$breakpoint-tablet) {
+ --#{tokens.$css-variable-prefix}card-padding: #{theme.$padding-tablet}; // 5.d
}
}
-.Card--boxed.Card--horizontal {
- &:has(.CardMedia--expanded) {
- grid-template-columns: auto 1fr;
- grid-template-rows:
- var(--#{tokens.$css-variable-prefix}card-padding)
- auto
- 1fr
- auto
- var(--#{tokens.$css-variable-prefix}card-padding); // 2.
-
- grid-template-areas:
- 'media .'
- 'media logo'
- 'media body'
- 'media footer'
- 'media .';
- }
+// 5.
+@each $breakpoint-name, $breakpoint-value in theme.$breakpoints {
+ $infix: breakpoint.get-modifier(infix, $breakpoint-name, $breakpoint-value);
- &:has(.CardMedia:not(:only-child)) {
- padding-right: var(--#{tokens.$css-variable-prefix}card-padding);
- }
-}
+ @include breakpoint.up($breakpoint-value) {
+ .Card--#{$infix}vertical {
+ --#{tokens.$css-variable-prefix}card-columns: #{map.get(theme.$layouts, vertical, columns)};
+ --#{tokens.$css-variable-prefix}card-rows: #{map.get(theme.$layouts, vertical, rows)};
+ --#{tokens.$css-variable-prefix}card-areas: #{map.get(theme.$layouts, vertical, areas)};
+ }
-.Card--boxed.Card--horizontalReversed {
- &:has(.CardMedia--expanded) {
- grid-template-columns: 1fr auto;
- grid-template-rows:
- var(--#{tokens.$css-variable-prefix}card-padding)
- auto
- 1fr
- auto
- var(--#{tokens.$css-variable-prefix}card-padding); // 2.
-
- grid-template-areas:
- '. media'
- 'logo media'
- 'body media'
- 'footer media'
- '. media';
- }
+ .Card--#{$infix}horizontal {
+ --#{tokens.$css-variable-prefix}card-columns: #{map.get(theme.$layouts, horizontal, columns)};
+ --#{tokens.$css-variable-prefix}card-rows: #{map.get(theme.$layouts, horizontal, rows)};
+ --#{tokens.$css-variable-prefix}card-areas: #{map.get(theme.$layouts, horizontal, areas)};
+ }
+
+ .Card--#{$infix}horizontalReversed {
+ --#{tokens.$css-variable-prefix}card-columns: #{map.get(theme.$layouts, horizontal-reversed, columns)};
+ --#{tokens.$css-variable-prefix}card-rows: #{map.get(theme.$layouts, horizontal-reversed, rows)};
+ --#{tokens.$css-variable-prefix}card-areas: #{map.get(theme.$layouts, horizontal-reversed, areas)};
+ }
+
+ .Card--#{$infix}vertical:has(.CardArtwork:not(:only-child), .CardMedia:not(:only-child)) {
+ --#{tokens.$css-variable-prefix}card-column-gap: 0;
+ }
+
+ :is(.Card--#{$infix}horizontal, .Card--#{$infix}horizontalReversed):has(
+ .CardArtwork:not(:only-child),
+ .CardMedia:not(:only-child)
+ ) {
+ --#{tokens.$css-variable-prefix}card-column-gap: #{theme.$gap};
+ }
+
+ // 5.c
+ .Card--boxed.Card--#{$infix}vertical {
+ &:has(.CardMedia--expanded) {
+ --#{tokens.$css-variable-prefix}card-columns: #{map.get(theme.$layouts-boxed, vertical, columns)};
+ --#{tokens.$css-variable-prefix}card-rows: #{map.get(theme.$layouts-boxed, vertical, rows)};
+ --#{tokens.$css-variable-prefix}card-areas: #{map.get(theme.$layouts-boxed, vertical, areas)};
+ }
- &:has(.CardMedia:not(:only-child)) {
- padding-left: var(--#{tokens.$css-variable-prefix}card-padding);
+ &:has(.CardMedia:not(:only-child)) {
+ --#{tokens.$css-variable-prefix}card-padding-shorthand: 0 0
+ var(--#{tokens.$css-variable-prefix}card-padding);
+ }
+ }
+
+ // 5.c
+ .Card--boxed.Card--#{$infix}horizontal {
+ &:has(.CardMedia--expanded) {
+ --#{tokens.$css-variable-prefix}card-columns: #{map.get(theme.$layouts-boxed, horizontal, columns)};
+ --#{tokens.$css-variable-prefix}card-rows: #{map.get(theme.$layouts-boxed, horizontal, rows)};
+ --#{tokens.$css-variable-prefix}card-areas: #{map.get(theme.$layouts-boxed, horizontal, areas)};
+ }
+
+ &:has(.CardMedia:not(:only-child)) {
+ --#{tokens.$css-variable-prefix}card-padding-shorthand: 0
+ var(--#{tokens.$css-variable-prefix}card-padding) 0 0;
+ }
+ }
+
+ // 5.c
+ .Card--boxed.Card--#{$infix}horizontalReversed {
+ &:has(.CardMedia--expanded) {
+ --#{tokens.$css-variable-prefix}card-columns: #{map.get(
+ theme.$layouts-boxed,
+ horizontal-reversed,
+ columns
+ )};
+ --#{tokens.$css-variable-prefix}card-rows: #{map.get(theme.$layouts-boxed, horizontal-reversed, rows)};
+ --#{tokens.$css-variable-prefix}card-areas: #{map.get(theme.$layouts-boxed, horizontal-reversed, areas)};
+ }
+
+ &:has(.CardMedia:not(:only-child)) {
+ --#{tokens.$css-variable-prefix}card-padding-shorthand: 0 0 0
+ var(--#{tokens.$css-variable-prefix}card-padding);
+ }
+ }
}
}
diff --git a/packages/web/src/scss/components/Card/_CardArtwork.scss b/packages/web/src/scss/components/Card/_CardArtwork.scss
index d5f18f3358..30899e1582 100644
--- a/packages/web/src/scss/components/Card/_CardArtwork.scss
+++ b/packages/web/src/scss/components/Card/_CardArtwork.scss
@@ -1,9 +1,15 @@
+@use '@tokens' as tokens;
+@use '../../tools/breakpoint';
@use '../../tools/dictionaries';
@use 'theme';
.CardArtwork {
display: grid;
grid-area: media;
+
+ &:not(:last-child) {
+ margin-bottom: var(--#{tokens.$css-variable-prefix}card-artwork-margin-bottom);
+ }
}
@include dictionaries.generate-alignments(
@@ -12,6 +18,16 @@
$axis: 'x'
);
-.Card--vertical > .CardArtwork:not(:last-child) {
- margin-bottom: theme.$gap;
+@each $breakpoint-name, $breakpoint-value in theme.$breakpoints {
+ @include breakpoint.up($breakpoint-value) {
+ $infix: breakpoint.get-modifier(infix, $breakpoint-name, $breakpoint-value);
+
+ .Card--#{$infix}vertical > .CardArtwork {
+ --#{tokens.$css-variable-prefix}card-artwork-margin-bottom: #{theme.$gap};
+ }
+
+ :is(.Card--#{$infix}horizontal, .Card--#{$infix}horizontalReversed) > .CardArtwork {
+ --#{tokens.$css-variable-prefix}card-artwork-margin-bottom: 0;
+ }
+ }
}
diff --git a/packages/web/src/scss/components/Card/_CardLogo.scss b/packages/web/src/scss/components/Card/_CardLogo.scss
index 36b14e4894..94d72dbc32 100644
--- a/packages/web/src/scss/components/Card/_CardLogo.scss
+++ b/packages/web/src/scss/components/Card/_CardLogo.scss
@@ -1,25 +1,39 @@
+@use '@tokens' as tokens;
+@use '../../tools/breakpoint';
@use 'theme';
.CardLogo {
display: inline-flex;
+ grid-area: var(--#{tokens.$css-variable-prefix}card-logo-grid-area);
+ place-self: var(--#{tokens.$css-variable-prefix}card-logo-place-self);
align-items: center;
justify-content: center;
+ margin-right: var(--#{tokens.$css-variable-prefix}card-logo-margin-right);
border: theme.$logo-border-width theme.$logo-border-style theme.$logo-border-color;
border-radius: theme.$logo-border-radius;
background-color: theme.$logo-background-color;
-}
-.Card--vertical > .CardLogo {
- grid-area: media;
- place-self: end;
- margin-right: theme.$logo-offset-horizontal;
+ &:not(:last-child) {
+ margin-bottom: var(--#{tokens.$css-variable-prefix}card-logo-margin-bottom);
+ }
}
-:is(.Card--horizontal, .Card--horizontalReversed) > .CardLogo {
- grid-area: logo;
- justify-self: start;
+@each $breakpoint-name, $breakpoint-value in theme.$breakpoints {
+ @include breakpoint.up($breakpoint-value) {
+ $infix: breakpoint.get-modifier(infix, $breakpoint-name, $breakpoint-value);
- &:not(:last-child) {
- margin-bottom: theme.$gap;
+ .Card--#{$infix}vertical > .CardLogo {
+ --#{tokens.$css-variable-prefix}card-logo-grid-area: media;
+ --#{tokens.$css-variable-prefix}card-logo-place-self: end;
+ --#{tokens.$css-variable-prefix}card-logo-margin-right: #{theme.$logo-offset-horizontal};
+ --#{tokens.$css-variable-prefix}card-logo-margin-bottom: 0;
+ }
+
+ :is(.Card--#{$infix}horizontal, .Card--#{$infix}horizontalReversed) > .CardLogo {
+ --#{tokens.$css-variable-prefix}card-logo-grid-area: logo;
+ --#{tokens.$css-variable-prefix}card-logo-place-self: start;
+ --#{tokens.$css-variable-prefix}card-logo-margin-right: 0;
+ --#{tokens.$css-variable-prefix}card-logo-margin-bottom: #{theme.$gap};
+ }
}
}
diff --git a/packages/web/src/scss/components/Card/_CardMedia.scss b/packages/web/src/scss/components/Card/_CardMedia.scss
index aa81c7271a..7218392bf1 100644
--- a/packages/web/src/scss/components/Card/_CardMedia.scss
+++ b/packages/web/src/scss/components/Card/_CardMedia.scss
@@ -2,26 +2,22 @@
// when the media is wrapped, for example in a link.
// 2. Make sure rounded corners are applied to video player too.
// 3. Do not let the media push the body content out of the horizontal card.
-// 4. We need the specific combination of classes, expanded CardMedia is designed to work only with boxed cards.
@use '@tokens' as tokens;
+@use '../../tools/breakpoint';
@use '../../tools/dictionaries';
@use 'theme';
.CardMedia {
- --#{tokens.$css-variable-prefix}card-media-border-radius-top-left: #{theme.$border-radius};
- --#{tokens.$css-variable-prefix}card-media-border-radius-top-right: #{theme.$border-radius};
- --#{tokens.$css-variable-prefix}card-media-border-radius-bottom-left: #{theme.$border-radius};
- --#{tokens.$css-variable-prefix}card-media-border-radius-bottom-right: #{theme.$border-radius};
- --#{tokens.$css-variable-prefix}card-media-canvas-width: auto;
- --#{tokens.$css-variable-prefix}card-media-canvas-height: auto;
-
grid-area: media;
+ align-self: var(--#{tokens.$css-variable-prefix}card-media-align-self);
+ min-width: 0; // 3.
overflow: hidden;
- border-radius: var(--#{tokens.$css-variable-prefix}card-media-border-radius-top-left)
- var(--#{tokens.$css-variable-prefix}card-media-border-radius-top-right)
- var(--#{tokens.$css-variable-prefix}card-media-border-radius-bottom-right)
- var(--#{tokens.$css-variable-prefix}card-media-border-radius-bottom-left);
+ border-radius: var(--#{tokens.$css-variable-prefix}card-media-border-radius-shorthand, #{theme.$border-radius});
+
+ &:not(:last-child) {
+ margin-bottom: var(--#{tokens.$css-variable-prefix}card-media-margin-bottom);
+ }
}
.CardMedia__canvas {
@@ -38,48 +34,50 @@
object-fit: cover;
}
-.Card--vertical > {
- @include dictionaries.generate-sizes($class-name: 'CardMedia', $sizes: theme.$media-sizes-vertical);
-}
-
-:is(.Card--horizontal, .Card--horizontalReversed) > {
- @include dictionaries.generate-sizes($class-name: 'CardMedia', $sizes: theme.$media-sizes-horizontal);
-}
-
-:is(.Card--horizontal, .Card--horizontalReversed) > .CardMedia {
- --#{tokens.$css-variable-prefix}card-media-canvas-width: var(--spirit-card-media-size);
-
- align-self: start;
- min-width: 0; // 3.
-}
-
-:is(.Card--horizontal, .Card--horizontalReversed) > :is(.CardMedia--expanded, .CardMedia--filledHeight) {
- --#{tokens.$css-variable-prefix}card-media-canvas-height: 100%;
-
- align-self: stretch;
-}
-
-.Card--vertical > .CardMedia {
- --#{tokens.$css-variable-prefix}card-media-canvas-height: var(--spirit-card-media-size);
-
- &:not(:last-child) {
- margin-bottom: theme.$gap-dense;
+@each $breakpoint-name, $breakpoint-value in theme.$breakpoints {
+ @include breakpoint.up($breakpoint-value) {
+ $infix: breakpoint.get-modifier(infix, $breakpoint-name, $breakpoint-value);
+
+ .Card--#{$infix}vertical > {
+ @include dictionaries.generate-sizes($class-name: 'CardMedia', $sizes: theme.$media-sizes-vertical);
+ }
+
+ :is(.Card--#{$infix}horizontal, .Card--#{$infix}horizontalReversed) > {
+ @include dictionaries.generate-sizes($class-name: 'CardMedia', $sizes: theme.$media-sizes-horizontal);
+ }
+
+ .Card--#{$infix}vertical > .CardMedia {
+ --#{tokens.$css-variable-prefix}card-media-canvas-width: auto;
+ --#{tokens.$css-variable-prefix}card-media-canvas-height: var(--spirit-card-media-size);
+ --#{tokens.$css-variable-prefix}card-media-align-self: initial;
+ --#{tokens.$css-variable-prefix}card-media-margin-bottom: #{theme.$gap-dense};
+ }
+
+ :is(.Card--#{$infix}horizontal, .Card--#{$infix}horizontalReversed) > .CardMedia {
+ --#{tokens.$css-variable-prefix}card-media-canvas-width: var(--spirit-card-media-size);
+ --#{tokens.$css-variable-prefix}card-media-canvas-height: auto;
+ --#{tokens.$css-variable-prefix}card-media-align-self: start;
+ --#{tokens.$css-variable-prefix}card-media-margin-bottom: 0;
+ }
+
+ :is(.Card--#{$infix}horizontal, .Card--#{$infix}horizontalReversed)
+ > :is(.CardMedia--expanded, .CardMedia--filledHeight) {
+ --#{tokens.$css-variable-prefix}card-media-canvas-height: 100%;
+ --#{tokens.$css-variable-prefix}card-media-align-self: stretch;
+ }
+
+ .Card--boxed.Card--#{$infix}vertical > .CardMedia--expanded:not(:last-child) {
+ --#{tokens.$css-variable-prefix}card-media-border-radius-shorthand: #{theme.$border-radius} #{theme.$border-radius}
+ 0 0;
+ }
+
+ .Card--boxed.Card--#{$infix}horizontal > .CardMedia--expanded:not(:last-child) {
+ --#{tokens.$css-variable-prefix}card-media-border-radius-shorthand: #{theme.$border-radius} 0 0 #{theme.$border-radius};
+ }
+
+ .Card--boxed.Card--#{$infix}horizontalReversed > .CardMedia--expanded:not(:last-child) {
+ --#{tokens.$css-variable-prefix}card-media-border-radius-shorthand: 0 #{theme.$border-radius} #{theme.$border-radius}
+ 0;
+ }
}
}
-
-// stylelint-disable selector-max-class -- 4.
-.Card--vertical.Card--boxed > .CardMedia--expanded:not(:last-child) {
- --#{tokens.$css-variable-prefix}card-media-border-radius-bottom-left: 0;
- --#{tokens.$css-variable-prefix}card-media-border-radius-bottom-right: 0;
-}
-
-.Card--horizontal.Card--boxed > .CardMedia--expanded:not(:last-child) {
- --#{tokens.$css-variable-prefix}card-media-border-radius-top-right: 0;
- --#{tokens.$css-variable-prefix}card-media-border-radius-bottom-right: 0;
-}
-
-.Card--horizontalReversed.Card--boxed > .CardMedia--expanded:not(:last-child) {
- --#{tokens.$css-variable-prefix}card-media-border-radius-top-left: 0;
- --#{tokens.$css-variable-prefix}card-media-border-radius-bottom-left: 0;
-}
-// stylelint-enable
diff --git a/packages/web/src/scss/components/Card/_theme.scss b/packages/web/src/scss/components/Card/_theme.scss
index 33b36ba6ce..1ffe08e355 100644
--- a/packages/web/src/scss/components/Card/_theme.scss
+++ b/packages/web/src/scss/components/Card/_theme.scss
@@ -4,6 +4,44 @@
$breakpoints: tokens.$breakpoints;
+$layouts: (
+ vertical: (
+ columns: 1fr,
+ rows: auto 1fr auto,
+ areas: "'media' 'body' 'footer'",
+ ),
+ horizontal: (
+ columns: auto 1fr,
+ rows: auto 1fr auto,
+ areas: "'media logo' 'media body' 'media footer'",
+ ),
+ horizontal-reversed: (
+ columns: 1fr auto,
+ rows: auto 1fr auto,
+ areas: "'logo media' 'body media' 'footer media'",
+ ),
+);
+
+$layouts-boxed: (
+ vertical: (
+ columns: var(--#{tokens.$css-variable-prefix}card-padding) 1fr var(--#{tokens.$css-variable-prefix}card-padding),
+ rows: var(--#{tokens.$css-variable-prefix}card-padding) auto 1fr auto,
+ areas: "'media media media' 'media media media' '. body .' '. footer .'",
+ ),
+ horizontal: (
+ columns: auto 1fr,
+ rows: var(--#{tokens.$css-variable-prefix}card-padding) auto 1fr auto
+ var(--#{tokens.$css-variable-prefix}card-padding),
+ areas: "'media .' 'media logo' 'media body' 'media footer' 'media .'",
+ ),
+ horizontal-reversed: (
+ columns: 1fr auto,
+ rows: var(--#{tokens.$css-variable-prefix}card-padding) auto 1fr auto
+ var(--#{tokens.$css-variable-prefix}card-padding),
+ areas: "'. media' 'logo media' 'body media' 'footer media' '. media'",
+ ),
+);
+
$gap: tokens.$space-900;
$gap-dense: tokens.$space-700;
diff --git a/packages/web/src/scss/components/Card/index.html b/packages/web/src/scss/components/Card/index.html
index 87e0d725f6..9d4b927f90 100644
--- a/packages/web/src/scss/components/Card/index.html
+++ b/packages/web/src/scss/components/Card/index.html
@@ -562,6 +562,77 @@
+
+ Responsive Card Layout
+
+
+
+
+
+
+
+
+ {{> web/assets/jobBoardLogo }}
+
+
+
+
Responsive card layout
+
+
+
Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
+
+
+
+
+
+
+
+
+
+ {{> web/assets/jobBoardLogo }}
+
+
+
+
Responsive card layout
+
+
+
Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
+
+
+
+
+
+
+
+
+
+