How to Create Headless Sitecore SXA Variants in XM Cloud

A practical approach to using variants in NextJS Headless environments, such as Sitecore XM Cloud.

June 27, 2023

By David Austin

One of the biggest draws to using SXA is the ability to use Variants. Give your authors some “variation” as to how the components you’ve built can be used. While this has been great, it wasn’t really available for the NextJs Headless architecture. With 10.3 and XM Cloud however, all that has changed and we get to enjoy SXA in all its glory, not just with variants.

Example: Light and Dark Variant in Headless SXA

For the purposes of this example I’m going to create a light and dark version of the Heading component. The light version being black text on a white background and the dark being white text on a black background.

We also want to ensure that when a component is added to the page, if no datasource is added to it, that it doesn’t break Experience Editor.

Like so:

Sitecore Items

How do we achieve that? Well first, let’s create our Heading variant structure. Under our Site node, let’s go to /Presentation/Headless Variants and create a variant with the same name as our tsx file. In our case, it’s Heading.tsx. Under that we will create a Variant Definition called Default, and then another called DarkHeading. We can use Display Name to make these more readable later but these two will match the name of the functions within our Heading.tsx file.

It is vital that one is called Default.

The Code

For the code itself, it’s broken down into three main functions.

First we have Default

export const Default = (props: HeadingProps): JSX.Element => {
  if (props.fields) {
    return (
      <div className="flex w-full">
        <div className="flex p-7 w-full">
          <Text
            tag="h1"
            className="relative font-black text-6xl lg:text-7xl leading-tight"
            field={props.fields.heading}
          />
        </div>
      </div>
    );
  }
  return <HeadingDefaultComponent />;
};

Then we have DarkHeading

export const DarkHeading = (props: HeadingProps): JSX.Element => {
  if (props.fields) {
    return (
      <div className="flex w-full">
        <div className="flex p-7 text-white bg-black w-full">
          <Text
            tag="h1"
            className="relative font-black text-6xl lg:text-7xl leading-tight"
            field={props.fields.heading}
          />
        </div>
      </div>
    );
  }
  return <HeadingDefaultComponent />;
};

And you’ll notice that both of these contain a fallback if no datasource is present.

export const HeadingDefaultComponent = (): JSX.Element => (
  <div className="flex component">
    <div className="component-content">
      <span className="is-empty-hint">No Datasource Present</span>
    </div>
  </div>
);

The entire file itself, looks like this:

import { Text, Field } from '@sitecore-jss/sitecore-jss-nextjs';
import { ComponentProps } from 'lib/component-props';

type HeadingProps = ComponentProps & {
  fields: {
    heading: Field<string>;
  };
};

export const HeadingDefaultComponent = (): JSX.Element => (
  <div className="flex component">
    <div className="component-content">
      <span className="is-empty-hint">No Datasource Present</span>
    </div>
  </div>
);

export const Default = (props: HeadingProps): JSX.Element => {
  if (props.fields) {
    return (
      <div className="flex w-full">
        <div className="flex p-7 w-full">
          <Text
            tag="h1"
            className="relative font-black text-6xl lg:text-7xl leading-tight"
            field={props.fields.heading}
          />
        </div>
      </div>
    );
  }
  return <HeadingDefaultComponent />;
};

export const DarkHeading = (props: HeadingProps): JSX.Element => {
  if (props.fields) {
    return (
      <div className="flex w-full">
        <div className="flex p-7 text-white bg-black w-full">
          <Text
            tag="h1"
            className="relative font-black text-6xl lg:text-7xl leading-tight"
            field={props.fields.heading}
          />
        </div>
      </div>
    );
  }
  return <HeadingDefaultComponent />;
};

We’re now able to use and select the appropriate variant depending on our need.

I’m sure you’ll be able to come up with a better use for variants but as you can see, they’re quite handy and very powerful.



Image of Fishtank employee David Austin

David Austin

Development Team Lead | Sitecore Technology MVP x 3

David is a decorated Development Team Lead with Sitecore Technology MVP and Coveo MVP awards, as well as Sitecore CDP & Personalize Certified. He's worked in IT for 25 years; everything ranging from Developer to Business Analyst to Group Lead helping manage everything from Intranet and Internet sites to facility management and application support. David is a dedicated family man who loves to spend time with his girls. He's also an avid photographer and loves to explore new places.