Conditionally styling code sections with Tailwind CSS and Next.js
Problem Statement
As part of this blog, there are a lot of code sections. However the theme for the code section has been fixed to Dark.
This was because, there is static theme available from highlight.js
like highlight.js/styles/github.css
.
The blog support light and dark theme based on your preferences (localstorage
) or media queries (system/browser wide theme).
Integrating both without manually writing markdown styles was complicated.
Solution
Usual dark theme style variations using css class names look like:
.my-style
{
color: black;
}
.dark .my-style
{
color: white;
}
The same can be represented in scss
more easily as:
.my-style
{
color: black;
}
.dark
{
.my-style
{
color: white;
}
}
Essentially anything we wrap under .dark
class will only apply to dark theme and then all themes can be loaded statically.
Extending the above logic to highlight.js
means something like:
// Contents of highlight.js/styles/github.css
.hljs{color:#24292e;} // and others
.dark
{
//Contents of highlight.js/styles/github-dark.css
.hljs{color:#c9d1d9;} // and others
}
Next step is to automate the above process during build. The highlevel break down on the steps is
- Read the style file from
highlight.js
module - Modify the style based on required prefix
- Save the file in desired path
The entire code looks something like below:
// prebuild.mts
import { readFile, writeFile, mkdir } from 'fs/promises';
import path from 'path';
const saveStyles = async (themePath: string, prefix?: string, targetPath: string = '/styles') => {
const target = path.join(process.cwd(), targetPath);
const content = await readFile(path.join(process.cwd(), 'node_modules', themePath));
const targetThemePath = path.join(target, themePath);
await mkdir(path.dirname(targetThemePath), { recursive: true });
const styleContent = prefix ? `.${prefix} { ${content} }` : content;
await writeFile(targetThemePath, styleContent, { encoding: 'utf-8', flag: 'w+' });
};
To save the files in styles
folder (default), call this function like
// prebuild.mts
await saveStyles('highlight.js/scss/github.scss');
await saveStyles('highlight.js/scss/github-dark.scss', 'dark');
The code is then called via a pre build step in package.json
// package.json
"scripts": {
"build": "yarn pre-build && next build",
"pre-build":"ts-node ./_scripts/prebuild.mts"
}
You will need to add ts-node
to your dev dependencies, enable ESM for the above to work.
// tsconfig.json
"ts-node": {
"esm": true,
"target": "nodenext",
},
Since the files are auto-generated during build, I have excluded them from source control with .gitignore
# .gitignore
#Generated Styles
styles/highlight.js
Including the files is now same as any other CSS file. In my case I have used TailwindCSS so it goes into my index.scss
// index.scss
@import './highlight.js/scss/github.scss';
@import './highlight.js/scss/github-dark.scss';
For any queries or feedback, please start a new discussion on GitHub Discussions or at Twitter @shubhan3009.
Photo Credit
Cover Photo by Ilja Tulit on Unsplash