Easily set up multi-theming in Sitecore JSS using Next.js and Tailwind CSS (Part 2)
In this article, we will explore the implementation of multiple themes within a single source code. Our approach to achieving multi-theming relies on React's context feature. To accomplish this, we encapsulate the entire application within the theme context.
Initiate multi theming
Before delving into the practical application of multi-theming, there's a preliminary step we need to take.
Head over to src/Layout.tsx
and import the useTheme
hook we crafted in the ThemeContext.tsx
file. Refer code snippet below to get more idea.
//Layout.tsx
import { useTheme } from 'lib/context/ThemeContext';
const Layout: React.FC<LayoutProps> = ({ layoutData }: LayoutProps): JSX.Element => {
//...Rest of the code
const { themeName } = useTheme();
//...Rest of the code
}
useTheme
will give us the themeName
variable which represents the current theme for the given site.
We need to add the current theme name as a class to a wrapper div that encompasses the entire application. Refer to line #9
in code snippet below to get more idea.
//Layout.tsx
import { useTheme } from 'lib/context/ThemeContext';
const Layout: React.FC<LayoutProps> = ({ layoutData }: LayoutProps): JSX.Element => {
//...Rest of the code
const { themeName } = useTheme();
//...Rest of the code
return (
<div className={classNames(mainClassPageEditing, themeName)}>
<header>
{/* Render placeholder for header */}
<div id="header">{route && <Placeholder name="headless-header" rendering={route} />}</div>
</header>
<main>
{/* Render placeholder for main content */}
<div id="content">{route && <Placeholder name="headless-main" rendering={route} />}</div>
</main>
<footer>
{/* Render placeholder for footer */}
<div id="footer">{route && <Placeholder name="headless-footer" rendering={route} />}</div>
<AlgoliaCrawler layoutData={layoutData} />
</footer>
</div>
);
}
Steps to use multi-theming
Follow below steps to create a component which can use multi theme. We’ll create a component named with HeroTwoColumn
in our example.
Create 2 files one is component and second is theme file.
HeroTwoColumn.tsx
//HeroTwoColumn.tsx import { useTheme } from 'lib/context/ThemeContext'; import { HeroTwoColumnTheme } from './HeroTwoColumn.theme'; export type HeroTwoColumnProps = Feature.EnterpriseWeb.Components.Hero.HeroTwoColumn; const HeroTwoColumn = (props: HeroTwoColumnProps) => { const { themeName, themeData } = useTheme(HeroTwoColumnTheme); if (!props.fields) return null; return ( <Component variant="1g" dataComponent="hero/herotwocolumn" gap="!gap-xxxs md:gap-s" {...props}> {themeName === 'primary' && ( <div className="col-span-12 md:col-span-1"> <span className="inline-block h-[3px] w-1 bg-primary md:w-full"></span> </div> )} <div className={`col-span-12 ${themeName === 'primary' ? 'md:col-span-5' : 'md:col-span-6'}`}> <Headline useTag="h1" classes={themeData.classes.headlineClass} {...props} /> </div> <div className="col-span-12 md:col-span-6"> <Subheadline useTag="h2" classes={themeData.classes.subheadlineClass} {...props} /> <BodyCopy classes={themeData.classes.bodyClass} {...props} /> <ButtonGroup classes={themeData.classes.buttonGroupClass} {...props} /> </div> </Component> ); }
HeroTwoColumn.theme.ts
//HeroTwoColumn.theme.ts import { ThemeFile } from 'lib/context/ThemeContext'; export const HeroTwoColumnTheme: ThemeFile = { primary: { classes: { headlineClass: 'text-sm-1 lg:text-1 font-heavy mb-s', subheadlineClass: 'text-sm-m 1g:text-m font-medium mb-s', bodyClass: 'text-body text-dark-gray font-regular mb-s', buttonGroupClass: { wrapper: 'flex-col', cta1Classes: 'mr-2 mb-m md:mb-0', cta2Classes: 'ml-xs md:ml-0 px-s md:px-0', }, }, }, secondary: { classes: { headlineClass: 'text-sm-m lg:text-1 font-medium', subheadlineClass: 'text-sm-m 1g:text-1 font-extra-light', bodyClass: 'text-body text-dark-gray font-regular mb-xxs', buttonGroupClass: { wrapper: 'flex-col md:items-center', cta1Classes: 'mr-2', cta2Classes: 'my-s md:my-0', }, }, }, };
HeroTwoColumn
contains of headline, subheadline, body and CTA elements. So, theme file contains classes as per the given elements in the component.Import
useTheme
context and pass the component’s theme object imported from theme file in it. Please refer line #7
inHeroTwoColumn.tsx
.useTheme
will return two variables -a.
themeName
- contains current theme name.b.
themeData
- contains the theme object from theme file.If current theme is
primary
, Component will be rendered usingprimary
object from theme file.For example if we think about
Headline
, We’ve passedthemeData.classes.headlineClass
in order to style it. Please refer line #20
inHeroTwoColumn.tsx
. If current theme isprimary
it’ll refer toheadlineClass
key at line #6
. If current theme issecondary
it’ll refer toheadlineClass
key at line #18
inHeroTwoColumn.theme.ts
.
In summary, we've effectively implemented multi-theming in our JSS application by following above steps. By leveraging this feature, we've enhanced developer experience while ensuring adaptability and scalability for future improvements.
Extra notes
HeroTwoColumn.theme.tsx
file outside the /src/components
folder. JSS treats all files within /src/components
as authorable components, and placing HeroTwoColumn.theme.ts
inside it would register it as a component in the JSS ecosystem.//Sample theme file for advance theming
import classNames from 'classnames';
import { ThemeFile, ThemeName } from 'lib/context/ThemeContext';
import { BorderStyle, TextAlignment } from './SectionHeadline';
const primaryHeadlineClasses = 'text-s md:text-m font-heavy pb-xs mb-s';
const secondaryHeadlineClasses = 'text-m md:text-l font-extra-light pb-xs mb-s';
const getBorderClasses = (theme: ThemeName, borderStyle: BorderStyle) => {
if (borderStyle !== 'default') {
let borderClasses = theme === 'primary' ? 'border-b-6 ' : 'border-b-3 ';
switch (borderStyle) {
case 'dark':
borderClasses += theme === 'primary' ? 'border-primary' : 'border-secondary';
break;
case 'light':
borderClasses += theme === 'primary' ? 'border-light-gray' : 'border-gray';
break;
default:
break;
}
return borderClasses;
}
return '';
};
const getDynamicStyles = (
theme: ThemeName,
alignment: TextAlignment,
borderStyle: BorderStyle
): string => {
return classNames(`text-${alignment}`, getBorderClasses(theme, borderStyle));
};
export const SectionHeadlineTheme = (
alignment: TextAlignment,
borderStyle: BorderStyle
): ThemeFile => {
return {
primary: {
classes: {
headlineContainer: classNames(
primaryHeadlineClasses,
getDynamicStyles('primary', alignment, borderStyle)
),
},
},
secondary: {
classes: {
headlineContainer: classNames(
secondaryHeadlineClasses,
getDynamicStyles('secondary', alignment, borderStyle)
),
},
},
};
};
Subscribe to my newsletter
Read articles from Saad Patel directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by