Targeting Pure Elements in Next.Js with CSS Modules

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:
Edit 68554803-next-js-cant-apply-dynamic-class-names

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:

  1. Accept the limitation and don't use implicit HTML selectors
  2. Modify next.config.js to change the behaviour of css-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



Leave a reply



Submit