Targeting Pure elements in next.js with CSS modules
Try giving a className or id to ul tag and then write your styles accordingly.
for example:
index.js
<div className={styles.container}>
<ul className={styles.container__list}>
<li>Person One</li>
</ul>
</div>
index.module.css
.container {
background-color: pink;
}
.container__list{
list-style-type: none;
}
Selector :root is not pure (pure selectors must contain at least one local class or id) - NextJS with SASS modules
After scouring the internet for a few hours I found a great solution from here: https://dhanrajsp.me/snippets/customize-css-loader-options-in-nextjs
EDIT: If you're using Next.js 12, check the bottom of the article above, because the solution is a little different.
You'll want to change your next.config.js file to include the following:
/** @type {import('next').NextConfig} */
require("dotenv").config();
const regexEqual = (x, y) => {
return (
x instanceof RegExp &&
y instanceof RegExp &&
x.source === y.source &&
x.global === y.global &&
x.ignoreCase === y.ignoreCase &&
x.multiline === y.multiline
);
};
// Overrides for css-loader plugin
function cssLoaderOptions(modules) {
const { getLocalIdent, ...others } = modules; // Need to delete getLocalIdent else localIdentName doesn't work
return {
...others,
localIdentName: "[hash:base64:6]",
exportLocalsConvention: "camelCaseOnly",
mode: "local",
};
}
module.exports = {
webpack: (config) => {
const oneOf = config.module.rules.find(
(rule) => typeof rule.oneOf === "object"
);
if (oneOf) {
// Find the module which targets *.scss|*.sass files
const moduleSassRule = oneOf.oneOf.find((rule) =>
regexEqual(rule.test, /\.module\.(scss|sass)$/)
);
if (moduleSassRule) {
// Get the config object for css-loader plugin
const cssLoader = moduleSassRule.use.find(({ loader }) =>
loader.includes("css-loader")
);
if (cssLoader) {
cssLoader.options = {
...cssLoader.options,
modules: cssLoaderOptions(cssLoader.options.modules),
};
}
}
}
return config;
},
};
I'm not seasoned with webpack or how it exactly works, but this solution worked for me. You can also change the regex to include css by doing (scss|sass|css) if you want.
Issue with :global() css-module selectors not being pure in NextJS
No there isn't any solution as of yet other than overriding the webpack config itself. It was working in CRA because they probably have mode
: local
, while Next.js has pure
.
I haven't tried overriding css-loader
webpack config, so I am simply suggesting a workaround. Since, you are using SCSS, you can wrap your pseudo-global [1] styles like this:
.root :global {
.foo {
color: red;
}
}
Now wrap your component/page in a div
and set the class as styles.root
on that element. Then, on all the child elements you can directly set className="foo"
.
import styles from "../styles/index.module.scss";
const IndexPage = () => (
<div className={styles.root}>
<div className="foo">This text should be red!</div>
</div>
);
export default IndexPage;
Note that, you need to consider issues regarding specificity after this method, also this doesn't directly work with animations, you need to separate the keyframes
and then make them global.
Demo Sandbox
[1]: This method doesn't make the styles truly global as the styles are still scoped. The class foo
will work only when some parent has styles.root
as class. This is preferrable only if you didn't intend to use your :global(.selector)
from other components, and were using them just because you wanted to manipulate the class names using JS without the styles object.
If you want these to be truly global, add styles.root
to document.documentElement
in an useEffect
hook like this:
import { useEffect } from "react";
import styles from "../styles/index.module.scss";
const IndexPage = () => {
useEffect(() => {
document.documentElement.classList.add(styles.root);
return () => {
document.documentElement.classList.remove(styles.root);
};
}, []);
return (
<div className="foo">
This text should be red, even if you put it in another component until the
page is same. If you want it across pages inject it in _app or _document.
</div>
);
};
export default IndexPage;
Demo Sandbox
PS: Injecting class to html
in _app
or _document
is not exactly same as using a global stylesheet, as it may happen that you have multi-page application, then only the CSS of the components on a particular page will be requested because of automatic CSS code-splitting done by Next.js. If that's not the case and all your pages share same CSS, then there is no need to complicate things, just go with the conventional method of importing styles in _app
.
Selector :global .class is not pure (pure selectors must contain at least one local class or id)
You need to use global selector inside your local selector in CSS-modules.
For example, if you have HTML:
<div className={classes.someCSSMoludesClass}>
<div className="some-global-class">
content
</div>
</div>
for rewriting global class "some-global-class" you need to make this inside your CSS-module:
.someCSSModulesClass {
:global(.some-global-class) {
%your properties%
}
}
Don't forget to use selector inside :global.
I had the same problem, but in swiper slider, and resolved it like this.
Maybe you have to write this class in the component that is above
Next.js - Cant apply dynamic class names when using CSS Modules
Because you asked nicely ;) (just kiddin')
So Next.js is an opinionated framework and uses CSS Modules to enforce component scoped styling.
Basically you define your stylesheet with a name.module.css
filename and add regular CSS in it.
.hidetheresult {
visibility: hidden;
}
.showtheresult{
visibility: visible;
}
.btn-hidetheresult {
border-color: pink;
}
.btn-showtheresult {
border-color: aqua;
}
Now to use this, import it like any JS module,
import styles from './styles.module.css'
console.log(styles);
// styles => {
// hidetheresult: 'contact_hidetheresult__3LvIF',
// showtheresult: 'contact_showtheresult__N5XLE',
// 'btn-hidetheresult': 'contact_btn-hidetheresult__3CQHv',
// 'btn-showtheresult': 'contact_btn-showtheresult__1rM1E'
// }
as you can see, the styles are converted to objects and now you can use them
like styles.hidetheresult
or styles['btn-hidetheresult']
.
Notice the absence of element selector in the stylesheet. That's because CSS Modules rewrite class names, but they don't touch tag names. And in Next.js that is
the default behaviour. i.e it does not allow element tag selectors.
File extensions with
*.module.(css | scss | sass)
are css modules and they can only target elements using classnames or ids and not using tag names. Although this is possible in other frameworks like create-react-app, it is not possible in next-js.
But you can override it in the next.config.js
file. (Beyond the scope of this answer)
There is an article which explains how to override it. - disclaimer: I am the author
Now coming to your use-case, you can do contitional styling like so: (assuming the styles are as per the sample given in the answer)
import React from "react";
import styles from "./styles.module.css";
const PageX = () => {
const [submitted, setSubmitted] = React.useState(false);
const getStyle = () => {
if (submitted) return styles.showtheresult;
else return styles.hidetheresult;
};
const getButtonStyle = () => {
if (submitted) return styles["btn-showtheresult"];
else return styles["btn-hidetheresult"];
};
return (
<div>
<section className="results">
<h1 className={getStyle()}>Correct?</h1>
<h1 className={getStyle()}>Incorrect?</h1>
<button className={getButtonStyle()} onClick={handleMovClick}>
An instruction
</button>
</section>
</div>
);
};
As you add more conditions, the methods do tend to get more complex. This is where the classnames
module comes handy.
import styles from "./styles.module.css";
import clsx from "classnames";
const PageX = () => {
const [submitted, setSubmitted] = React.useState(false);
const headerStyle = clsx({
[styles.showtheresult]: submitted,
[styles.hidetheresult]: !submitted,
});
const btnStyle = clsx({
[styles["btn-showtheresult"]]: submitted,
[styles["btn-hidetheresult"]]: !submitted,
});
return (
<div>
<section className="results">
<h1 className={headerStyle}>Correct?</h1>
<h1 className={headerStyle}>Incorrect?</h1>
<button className={btnStyle} onClick={handleMovClick}>
An instruction
</button>
</section>
</div>
);
};
Here's a CodeSandbox for you to play with:
I can't style any html/jsx tag in css, because it says 'h1 selector is not pure'
Next.js is utlising built-in css-loader
that is configured so that all the selectors require to be pure - which means that you can not target elements by their tag. I think that this setting has been added in one of the releases after v9.0.
The topic is discussed in-depth here and in some other issues reported on their github repo. AFAIK it's working as intended and the only way to go around it is:
- Accept the limitation and don't use implicit HTML selectors
- Modify
next.config.js
to change the behaviour ofcss-loader
In the end the enforced limitation is good and pushes you to use better CSS practices, however it proves to be a big obstacle for bigger projects with lots of legacy stylesheets to migrate into Next.js.
Related Topics
Attribute Onclick="Function()" Not Functioning as Intended
Impossible to Hide Navigation Bars in Safari iOS 7 for Iphone/Ipod Touch
How to Host Material Icons Offline
Twitter's Typeahead.Js Suggestions Are Not Styled (Have No Border, Transparent Background, etc.)
Include CSS and JavaScript in My Django Template
Android Keyboard Shrinking the Viewport and Elements Using Unit Vh in CSS
Get the Height of an Element Minus Padding, Margin, Border Widths
Google Maps Zoom Control Is Messed Up
How to Set Active Class to Nav Menu from Twitter Bootstrap
Duplicating an Element (And Its Style) with JavaScript
Serving High Res Images to Retina Display
How to Append a CSS Class to an Element by JavaScript
How to Style a Title? (And with CSS or Js)
Using JavaScript to Compare Two Input Numbers in HTML
Handling "Onclick" Event with Pure JavaScript