| @use "config" as *; |
| @use "functions" as *; |
| @use "mixins/border-radius" as *; |
| @use "mixins/box-shadow" as *; |
| @use "mixins/tokens" as *; |
| @use "layout/breakpoints" as *; |
| |
| $card-tokens: () !default; |
| |
| // scss-docs-start card-tokens |
| // stylelint-disable-next-line scss/dollar-variable-default |
| $card-tokens: defaults( |
| ( |
| --card-spacer-y: var(--spacer-5), |
| --card-spacer-x: var(--spacer-5), |
| --card-subtitle-color: inherit, |
| --card-border-width: var(--border-width), |
| --card-border-color: var(--border-color-translucent), |
| --card-border-radius: var(--radius-8), |
| --card-box-shadow: none, |
| --card-inner-border-radius: calc(var(--card-border-radius) - var(--border-width)), |
| --card-cap-padding-y: var(--spacer-3), |
| --card-cap-padding-x: var(--spacer), |
| --card-cap-bg: var(--bg-1), |
| --card-cap-color: inherit, |
| --card-height: auto, |
| --card-color: inherit, |
| --card-bg: var(--bg-body), |
| --card-img-overlay-padding: var(--card-spacer-y), |
| --card-group-margin: #{$grid-gutter-x * .5}, |
| --card-body-gap: calc(var(--card-spacer-y) * .5), |
| ), |
| $card-tokens |
| ); |
| // scss-docs-end card-tokens |
| |
| @layer components { |
| .card { |
| @include tokens($card-tokens); |
| |
| position: relative; |
| display: flex; |
| flex-direction: column; |
| min-width: 0; // See https://github.com/twbs/bootstrap/pull/22740#issuecomment-305868106 |
| height: var(--card-height); |
| color: var(--fg-body); |
| word-wrap: break-word; |
| background-color: var(--card-bg); |
| @include border-radius(var(--card-border-radius)); |
| @include box-shadow(var(--card-box-shadow)); |
| |
| > hr { |
| margin-inline: 0; |
| } |
| } |
| |
| .card-body { |
| display: flex; |
| // Enable `flex-grow: 1` for decks and groups so that card blocks take up |
| // as much space as possible, ensuring footers are aligned to the bottom. |
| flex: 1 1 auto; |
| flex-direction: column; |
| gap: var(--card-body-gap); |
| align-items: flex-start; |
| padding: var(--card-spacer-y) var(--card-spacer-x); |
| color: var(--card-color); |
| border: solid var(--theme-bg, var(--card-border-color)); |
| border-width: 0 var(--card-border-width); |
| |
| > * { |
| margin-block: 0; |
| } |
| } |
| |
| .card-body, |
| .card-list { |
| border: solid var(--theme-bg, var(--card-border-color)); |
| border-width: 0 var(--card-border-width); |
| |
| &:first-child { |
| @include border-top-radius(var(--card-border-radius)); |
| border-top-width: var(--card-border-width); |
| } |
| |
| &:last-child { |
| @include border-bottom-radius(var(--card-border-radius)); |
| border-bottom-width: var(--card-border-width); |
| } |
| |
| &:not(:first-child, :last-child) { |
| border-block-end-width: var(--card-border-width); |
| } |
| |
| // The footer draws a full border (including its top edge), so a body/list |
| // segment that precedes it must not also draw a bottom border or the seam |
| // doubles up. |
| &:has(+ .card-footer) { |
| border-block-end-width: 0; |
| } |
| } |
| |
| .card-subtitle { |
| margin-top: calc(var(--card-body-gap) * -.5); |
| } |
| |
| .card-header { |
| padding: var(--card-cap-padding-y) var(--card-cap-padding-x); |
| margin-bottom: 0; // Removes the default margin-bottom of <hN> |
| color: var(--theme-contrast, var(--card-cap-color)); |
| background-color: var(--theme-bg, var(--card-cap-bg)); |
| border: var(--card-border-width) solid var(--theme-bg, var(--card-border-color)); |
| |
| &:first-child { |
| @include border-radius(var(--card-inner-border-radius) var(--card-inner-border-radius) 0 0); |
| } |
| } |
| |
| .card-footer { |
| padding: var(--card-cap-padding-y) var(--card-cap-padding-x); |
| color: var(--card-cap-color); |
| background-color: var(--theme-bg, var(--card-cap-bg)); |
| border: var(--card-border-width) solid var(--theme-bg, var(--card-border-color)); |
| |
| &:last-child { |
| @include border-radius(0 0 var(--card-inner-border-radius) var(--card-inner-border-radius)); |
| } |
| } |
| |
| .card-translucent { |
| background-color: color-mix(in oklch, var(--card-bg) 80%, transparent); |
| backdrop-filter: blur(5px) saturate(180%); |
| |
| .card-header, |
| .card-footer { |
| background-color: color-mix(in oklch, var(--card-cap-bg) 60%, transparent); |
| } |
| } |
| |
| .card-subtle { |
| border-color: var(--theme-border, var(--card-border-color)); |
| |
| .card-header { |
| color: var(--theme-fg-emphasis, currentcolor); |
| background-color: var(--theme-bg-subtle, var(--card-cap-bg)); |
| border-color: var(--theme-border, var(--card-border-color)); |
| } |
| |
| .card-footer { |
| color: var(--theme-fg-emphasis, currentcolor); |
| background-color: var(--theme-bg-subtle, var(--card-cap-bg)); |
| border-color: var(--theme-border, var(--card-border-color)); |
| } |
| |
| .card-body, |
| .card-list { |
| border-color: var(--theme-border, var(--card-border-color)); |
| } |
| } |
| |
| // |
| // Header navs |
| // |
| |
| // Combined selector because of specificity match with `.nav` base class |
| .nav.card-header-tabs { |
| margin-inline: calc(-.5 * var(--card-cap-padding-x)); |
| margin-bottom: calc(-1 * var(--card-cap-padding-y) - var(--nav-tabs-border-width)); |
| border-block-end: 0; |
| |
| .nav-link.active { |
| background-color: var(--card-bg); |
| border-block-end-color: var(--card-bg); |
| } |
| } |
| |
| // Card image |
| .card-img-overlay { |
| position: absolute; |
| inset: 0; |
| padding: var(--card-img-overlay-padding); |
| @include border-radius(var(--card-inner-border-radius)); |
| } |
| |
| .card-img, |
| .card-img-top, |
| .card-img-bottom { |
| width: 100%; // Required because we use flexbox and this inherently applies align-self: stretch |
| outline: var(--card-border-width) solid var(--card-border-color); |
| outline-offset: calc(var(--card-border-width) * -1); |
| } |
| |
| .card-img { |
| @include border-radius(var(--radius-5)); |
| } |
| |
| .card-img-top { |
| @include border-top-radius(var(--card-inner-border-radius)); |
| } |
| |
| .card-img-bottom { |
| @include border-bottom-radius(var(--card-inner-border-radius)); |
| } |
| |
| .card-row { |
| flex-direction: row; |
| |
| .card-body, |
| .card-list { |
| border-width: var(--card-border-width) 0; |
| @include border-radius(0); |
| |
| &:first-child { |
| @include border-start-radius(var(--card-inner-border-radius)); |
| border-inline-start-width: var(--card-border-width); |
| } |
| |
| &:last-child { |
| @include border-end-radius(var(--card-inner-border-radius)); |
| border-inline-end-width: var(--card-border-width); |
| } |
| |
| &:not(:first-child, :last-child) { |
| border-inline-end-width: var(--card-border-width); |
| } |
| } |
| } |
| |
| .card-img-start { |
| @include border-start-radius(var(--card-inner-border-radius)); |
| } |
| |
| .card-img-end { |
| @include border-end-radius(var(--card-inner-border-radius)); |
| } |
| |
| // |
| // Card groups |
| // |
| |
| // Card groups lay out their cards in a row using a container query, so wrap the |
| // group in a query container (e.g., the `.contains-inline` utility) for the row |
| // layout to take effect. Without a query container the cards remain stacked. |
| .card-group { |
| // The child selector allows nested `.card` within `.card-group` |
| // to display properly. |
| > .card { |
| margin-bottom: var(--card-group-margin); |
| } |
| |
| @include container-breakpoint-up(sm) { |
| display: flex; |
| flex-flow: row wrap; |
| // The child selector allows nested `.card` within `.card-group` |
| // to display properly. |
| > .card { |
| flex: 1 0 0; |
| margin-bottom: 0; |
| |
| // Borders now live on the inner segments (header, body, list, footer) |
| // and the card images use outlines, so adjacent cards would otherwise |
| // render a doubled-up border at each seam. Pull subsequent cards back by |
| // one border width so their leading edges overlap the previous card's |
| // trailing edge, collapsing the seam into a single line. Gap can't be |
| // negative, so this relies on a negative margin. |
| + .card { |
| margin-inline-start: calc(-1 * var(--card-border-width)); |
| } |
| |
| // Handle rounded corners |
| @if $enable-rounded { |
| &:not(:last-child) { |
| @include border-end-radius(0); |
| |
| > .card-img-top, |
| > .card-header, |
| > .card-body { |
| border-start-end-radius: 0; |
| } |
| > .card-img-bottom, |
| > .card-footer, |
| > .card-body { |
| border-end-end-radius: 0; |
| } |
| } |
| |
| &:not(:first-child) { |
| @include border-start-radius(0); |
| |
| > .card-img-top, |
| > .card-header, |
| > .card-body { |
| border-start-start-radius: 0; |
| } |
| > .card-img-bottom, |
| > .card-footer, |
| > .card-body { |
| border-end-start-radius: 0; |
| } |
| } |
| } |
| } |
| } |
| } |
| } |