Happy Pride Month! - Also, GDS v3 is live! 💅

Migration Guide for CardGroup

CardGroup has been DEPRECATED, from now on a single Card can accomplish its tasks. In doing so, individual CardBodies take up some responsibilities. To get the idea, please read this document in its entirety.

The single most important prop for this migration is the new nested prop on the Card component. During the migration phase, nested is a fake boolean, as it has three states: false, true, and undefined.

  • To indicate that a Card is replacing a CardGroup, the nested prop must be set to false. This should only(!) be done at the topmost Card, i.e. on grey background.
  • Leaving it undefined is an indicator that a Card is not yet part of the post-CardGroup world.
  • Setting nested to true should only be done for nested Cards inside a nested={false} Card, and whenever a topmost Card is placed on white background, most likely a Modal. Nested Cards are visually distinct, because they have a 1px border around them. Although GDS can detect nested cards automatically, we use this migration as an opportunity to clearly mark nested Cards as such, to future-proof things.

Setting nested to false or true additionally bypasses useCardBody in the sense that not adding a CardBody is implied.

HINT Once the migration is complete, the undefined state will be removed from nested, and it then defaults to false.

Vertical CardGroup

Given that CardGroup and Card are among the most-used components of GDS, the migration phase may be done in two steps. This is possible because all not-yet-migrated Cards inside a nested={false|true} Card turn into empty shells without any visual characteristics.

Step 1: Replace CardGroup with Card.

In this scenario, the individual Cards inside the new topmost Card are kept, so the initial goal is to move away from CardGroup, by replacing CardGroups – or even a series of CardGroup siblings conjoined with noGap – with a single nested={false} wrapper Card.

jsx// old
<CardGroup noLine>
  <CardHeader>H</CardHeader>
  <Card>1</Card>
  <Card>2</Card>
  <CardFooter>F</CardFooter>
</CardGroup>

// new-ish
<Card nested={false}>
  <CardHeader>H</CardHeader>
  <Card>1</Card>
  <Card>2</Card>
  <CardFooter>F</CardFooter>
</Card>

Untouched Cards inside the now gone CardGroups continue to have nested={undefined}, as they are still considered to be legacy Cards which will be turned into CardBodies in step 2. Truly nested Cards with the visible 1px border around them should be marked as nested={true}.

jsx// old
<CardGroup noLine>
  …
  <Card>
    <Card>Nested Card</Card>
  </Card>
  …
</CardGroup>

// new-ish
<Card nested={false}>
  …
  <Card>
    <Card nested={true}>Nested Card</Card>
  </Card>
  …
</Card>

Step 2: Turn remaining Cards into CardBodies.

In the second step, all nested={undefined} Cards will be replaced. In most cases, a CardBody component will be the replacement, but if your to-be-migrated Card has padding set to NONE, a CardBody may not even be needed, as most components can be placed directly inside a new nested={false} Card.

jsx// old
<CardGroup noLine>
  <CardHeader>H</CardHeader>
  <Card>1</Card>
  <Card>2</Card>
  <CardFooter>F</CardFooter>
</CardGroup>

// new-ish
<Card nested={false}>
  <CardHeader>H</CardHeader>
  <Card>1</Card>
  <Card>2</Card>
  <CardFooter>F</CardFooter>
</Card>

// new
<Card nested={false}>
  <CardHeader>H</CardHeader>
  <CardBody>1</CardBody>
  <CardBody>2</CardBody>
  <CardFooter>F</CardFooter>
</Card>

The two-step migration approach is also applicable for everything that follows. However, the upcoming chapters only show old and new, skipping “new-ish” for brevity.

Horizontal CardGroup

The new CardRow component has been introduced to replace CardGroup.horizontal. To quote the Card documentation page, …

CardRow is not a “row of Cards”, but a row of components inside a Card, in most cases a row of CardBodies.

Like horizonal CardGroups, components in a CardRow run from left to right on larger tiers, but run top to bottom below MD.

jsx// old: horizontal only
<CardGroup noLine horizontal>
  <Card>.1</Card>
  <Card>.2</Card>
  <Card>.3</Card>
</CardGroup>

// old: horizontal, but part of something bigger
<CardGroup noGap noLine>1</CardGroup>
<CardGroup noGap noLine horizontal>
  <Card>2.1</Card>
  <Card>2.2</Card>
  <Card>2.3</Card>
</CardGroup>
<CardGroup>3</CardGroup>

// new: one solution for both cases
<Card nested={false}>
  <CardBody>1</CardBody>
  <CardRow>
    <CardBody>2.1</CardBody>
    <CardBody>2.2</CardBody>
    <CardBody>2.3</CardBody>
  </CardRow>
  <CardBody>3</CardBody>
</Card>

Separated Cards

Currently, when noLine is not set on a CardGroup, borders are added around the contained Cards. In a post-CardGroup world, separators between CardBodies may/must to be set manually with the Separator component. Although it’s an extra step, it opens up new possibilities.

jsx// old
<CardGroup noGap>
  <Card>1</Card>
</CardGroup>
<CardGroup noGap>
  <Card>2</Card>
</CardGroup>
<CardGroup>
  <Card>3</Card>
</CardGroup>

// new
<Card nested={false}>
  <CardBody>1</CardBody>
  <Separator />
  <CardBody>2</CardBody>
  <Separator />
  <CardBody>3</CardBody>
</Card>

HINT Setting nested={false} on the topmost Card is primarily needed to suppress the existing border logic, so they do not interfere with the actual Separator.


This is how the new Card nesting works. So far we didn’t talk about FunctionCards. Guess what, they too wanna be part of this new reality.

FunctionCards

Horizontal FunctionCards always had to be part of a CardGroup, but from now on they should live inside a Card instead. Do not forget to add separator lines between them.

jsx// old
<CardGroup>
  <FunctionCard horizontal>hF1</FunctionCard>
  <FunctionCard horizontal>hF2</FunctionCard>
</CardGroup>

// new
<Card nested={false}>
  <FunctionCard horizontal>hF1</FunctionCard>
  <Separator />
  <FunctionCard horizontal>hF2</FunctionCard>
</Card>

VerticalFunctionCardGroup

To arrange vertical Functioncards, we’ve used the dedicated helper component VerticalFunctionCardGroup, which creates a custom card group under the hood. From now on, nothing special is needed anymore, Card and CardRow will take over, so VerticalFunctionCardGroup has been DEPRECATED.

jsx// old
<VerticalFunctionCardGroup>
  <FunctionCard>vF1</FunctionCard>
  <FunctionCard>vF2</FunctionCard>
</VerticalFunctionCardGroup>

// new
<Card nested={false}>
  <CardRow>
    <FunctionCard>vF1</FunctionCard>
    <Separator />
    <FunctionCard>vF2</FunctionCard>
  </CardRow>
</Card>

Replacing a VerticalFunctionCardGroup that has the noline prop set is even easier, simply do not add Separators between the FunctionCards.

Another feature of VerticalFunctionCardGroup is the shared FunctionCard footer. To replace a VerticalFunctionCardGroup that has the footer prop set, add a regular CardFooter as the last element in the CardRow.

jsx// old
<VerticalFunctionCardGroup footer="F">
  <FunctionCard>vF1</FunctionCard>
  <FunctionCard>vF2</FunctionCard>
  <FunctionCard>vF3</FunctionCard>
</VerticalFunctionCardGroup>

// new
<Card nested={false}>
  <CardRow>
    <FunctionCard>vF1</FunctionCard>
    <FunctionCard>vF2</FunctionCard>
    <FunctionCard>vF3</FunctionCard>
    <CardFooter>F</CardFooter>
  </CardRow>
</Card>

Emphasis on CardRow! If you put it after the CardRow, you’ll end up with a regular CardFooter.


That’s it, unless you gave to migrate pages in the George Store. In that case, keep reading.

Pages (George Store)

Product Page

With the shift from CardGroup to Card, the modifier classNames on product pages must be split up. The Card still has .g-card-product (which is now only needed once), but other modifiers are moved down to the individual CardBody areas.

Separators may be added when needed, usually between two CardBodies without a background color, i.e. default background.

jsx// old
<CardGroup>
  …
  <Card className="g-card-product g-card-product-body">n of N</Card>
  <Card className="g-card-product g-card-product-footer">F</Card>
</CardGroup>

// new
<Card nested={false} className="g-card-product">
  …
  <CardBody className="g-card-product-body">n of N</CardBody>
  <Separator />
  <CardBody className="g-card-product-footer">F</CardBody>
</Card>

Checkout Page

What has been said about product pages is also true for checkout pages, here the .g-card-checkout modifier className is kept on the Card, and once again other modifiers are moved down to the individual CardBody areas.

jsx// no example, same as above, but with "-checkout" instead of "-product"

Confirm Page

Confirmation pages can be at the end of a series of product or checkout pages. Here too we proceed as previously described, but with one difference: Both .g-card-checkout and .g-card-checkout-confirm remain on the Card, and a newly introduced .g-card-confirm-body must be added to CardBodies that are not a footer area, to match the existing naming conventions.

jsx// old
<CardGroup noLine>
  …
  <Card className="g-card-checkout g-card-checkout-confirm">n of N</Card>
  <Card className="g-card-checkout g-card-confirm-footer">F</Card>
</CardGroup>

// new
<Card nested={false} className="g-card-checkout g-card-checkout-confirm">
  …
  <CardBody className="g-card-confirm-body">n of N</CardBody>
  <CardBody className="g-card-confirm-footer">F</CardBody>
</Card>

You’ve made it this far. Hopefully, all’s well that ends well.