I am relatively new to Next.js and the react ecosystem, but have been using it for a lot of my website development lately and so far have been loving it. One thing that is definitely causing some increased development time is how the styling works using SCSS, or at least my understanding of it.
I have built a number of SCSS partial files that contain different pieces of a style guide that I have used for a while - think similar to tailwind where I have predefined a large number of classes to do different things, some as simple as .flex { display: flex; } to more complex styles.
I have imported many of these partials into the globals.scss file so they would be applied everywhere, and some of the other partials are only imported into the specific files that I need them.
The issue I am running into with Next is that if I want to style a global selector, I have to add additional classes to the element. For example, if I had something like this
import styles from './component.module.scss'
export default function Component() {
return (
<div className={styles.classOne}>
<div className="container additionalClass anotherClass"></div>
</div>
);
what would be ideal is to be able to target those classes in the related scss file like so:
.classOne {
background:#000;
.additionalClass {
padding:10px;
}
}
however targeting that .additionalClass does not work, so I would have to switch the js file to be something like
import styles from './component.module.scss'
export default function Component() {
return (
<div className={styles.classOne}>
<div className={`container additionalClass anotherClass ${styles.mySpecificStyleSelector}`}></div>
</div>
);
and then target .mySpecificStyleSelector in the scss. Is this something that is by design with Next or am I missing something that could help with this scenario? I appreciate any input!
Related
So I'm migrating an app from CRA to NextJS and I have encountered an error for the .module.scss files of some components and pages:
Syntax error: Selector ":global(.label-primary)" is not pure (pure selectors must contain at least one local class or id)
I get this error for all the :global and :local css-module selectors. Based on what I have searched I can fix this issue by wrapping the selector in a class and editing the jsx aswell. but wouldn't that defeat it's purpose?
And how is this working on the CRA version of the app and not on NextJS?
EDIT:
One solution I have for this is moving :global() selectors to the global css files that are imported in _app.js but my question is that is there any way that we can have so these styles would be usable like they are right now ( :global(...) )?
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.
I had the same problem, the right writing is
.root:global {
color:red
}
Another approach is to make a container, wrap it, and carry it out like follows:
import style from '../styles/style.module.css'
<div className={styles.container}>
<p>Hello World</p>
</div>
.container p {
font-size: 20px;
}
And yes, you only need to include your tags and conditions in the CSS file if you have a lot of them.
I need to hash (or just prefix_) all CSS class names in a Vue CLI project to avoid class inheritance when I embed the app inside an existing HTML page.
Basically the Vue app/widget I've made uses some CSS class names like .title, .container, .date, .location etc, and the problem I've got is that the global CSS on the website where I'm embedding this Vue app already uses the class names .title, .container etc, so it's applying those styles to my Vue app as well. I just want a simple way — maybe using vue.config.js — to instruct webpack to randomly hash or prefix the CSS class names so that they are completely unique and won't inherit any parent styling. Thanks
Thanks to the comments above I managed to get it to work. The implementation is not as straight forward as I'd have liked because it requires changing a lot of my existing code — I was hoping for a quicker, simpler solution that would just take my existing code and hash the preexisting CSS classes — which I'm pretty sure must be possible as it seems like such a trivial task? Anyway, here's my setup for now:
vue.config.js
module.exports = {
css: {
requireModuleExtension: false,
loaderOptions: {
css: {
modules: {
localIdentName: '[hash:6]'
}
}
}
}
}
my-component.vue
<template>
<div :class="$style.myClass"> ... </div>
</template>
<style module>
.myClass {
color: red;
}
</style>
Using the above setup I get a div which, instead of being:
<div class="myClass"> ... </div>
is now rendered as:
<div class="_2d736c"> ... </div>
Note the module attribute on the <style> tag which is important. Also note the class name binding: :class (or v-bind:class) not just class.
Hope that helps someone. Thanks
In my project, I use CSS Modules with Less, which means I get the best of both worlds.
My src folder looks something like this:
components/
[all components]
theme/
themes/
lightTheme.less
darkTheme.less
palette.less
palette.less:
#import './themes/lightTheme.less';
Then, in every component that wants to use colors from the theme, I do:
component.module.less:
#import '../../theme/palette.less';
.element {
background-color: #primary;
}
This structure lets me edit palette.less to import the theme I want to use. The thing is that I want to let the users choose their preferred theme on their own. Themes should be switchable on runtime, which means I somehow need to have both themes compiled.
I imagine the perfect solution to be something like this:
app.less
body {
#theme: #light-theme;
&.dark-theme {
#theme: #dark-theme;
}
}
And then somehow import this #theme variable in every component and read properties from it (i.e. #theme[primary]).
Unfortunately, Less variables scoping don't work like this.
I am open-minded to any solution that uses Less modules.
Thank you!
I know that you've probably looking for a solution that uses Less / CSS modules, but it's very likely that your situation can be solved solely with the use of css variables (as Morpheus commented on your question).
How it would work?
You'd have to ensure all your styling does not use hardcoded values, i.e. instead of:
.awesome-div {
background-color: #fefefe;
}
You would have:
:root {
--awesome-color: #fefefe;
}
.awesome-div {
background-color: var(--awesome-color);
}
Changing between light and dark
There are two ways of changing themes in this approach:
Use vanilla Js code within React to update the :root CSS element, check this codepen for more information;
Just load a component containing all new :root variables in its component.css file;
In React (in vanilla CSS too) you can easily have multiple components/elements declaring their own :root at their .css files.
Furthermore, any new :root will overwrite conflicting values from previous :root. For example if at file app.css we have :root { --color: red; } and, when loading another component, component A for instance, where in component_a.css we have the same variable overwritten, e.g. :root { --color: blue; } the one rendered in our browsers will be the one from component A.
Following this logic, you can have a dummy component that does and renders exactly nothing, but instead in this component.js file you import the .css of a theme, e.g.:
import './light.css'; // suppose this is the light-theme dummy component
When switching themes in your app you'd just have to remove the dummy component from scene and call the other one.
I'm not too experienced with codepen to the point of providing you an example containing imports/modules over there, but I hope the above explanation can give you an idea of what I mean. Still, here's a brief pseudo-code of what I'm intending to demonstrate:
loadTheme() {
if (this.state.theme === 'dark') return <LightTheme />;
if (this.state.theme === 'user-3232') return <UserTheme />;
return <DarkTheme />;
}
render() {
return (
<App>
{this.loadTheme()}
<OtherContent>
</App>
);
}
Check out Styled components, it can do that.
https://www.npmjs.com/package/styled-components
https://styled-components.com/docs/advanced#theming
I did it myself as a Easter Egg in an app of mine, so I know for sure it works. Unfortunately it is closed so I can't show you the code publicly.
I have understood the concept of CSS modules so much that I am convinced that I do not want to do anything else that that for the future.
Currently I am trying to refactor an existing app to use CSS modules, the app has used classic sass with BEM methodology since.
Before I describe my problem I want to make clear that I undestand that I am addressing an issue that is not really within the domain of CSS modules. One should apply styles solely for usage inside a single module. At the most one should compose CSS classes with other CSS classes of other modules. But basically: You build an (HTML-)module and you use CSS modules to style that module and that's that.
Here's the problem:
In the process of refactoring there is one single issue that derives from having had a SASS-based style system. I can't find a valid method to work with a CSS class within a CSS modules environment when this class should work in combination of another class from another module.
Example in SASS:
[page.scss]
.wrapper {
margin: 0;
}
[headline.scss]
.headline {
color: green;
}
.wrapper {
.headline {
color: orange;
}
}
As you can see: One module (page) defines a CSS class "wrapper", another module defines a CSS class "headline". And, additionally, the class "headline" should behave a bit differently when placed inside the class "wrapper".
Again, I know that this is not really the domain of CSS modules. But I really would like to know if this is somehow doable with CSS modules? The "composes"-feature of CSS modules does not really fit here...
This is a common issue when migrating to CSS Modules. In short, a css module cannot override a style from another css module, and this is by design. Styles are supposed to live with the components that render them, and nowhere else.
What you can do to refactor this is to create a component style variant and explicitly set the variant through a prop when rendered within your wrapper.
For example, suppose your headline component currently looks something like this:
CSS
.headline {
color: green;
}
JSX
import styles from "Headline.css";
const Headline = () => {
return (
<div className={styles.headline} />
);
}
Rather than trying to override the .headline class name from somewhere else, you can create a variant class name that you toggle through a prop:
CSS
.headline-green {
color: green;
}
.headline-orange {
color: orange;
}
JSX
import styles from "Headline.css";
const Headline = ({orange}) => {
return (
<div className={orange ? styles.headlineOrange : styles.headlineGreen} />
);
}
And when you render it from your wrapper, set it to the orange variant:
<Headline orange />
Tip: you can use composes to eliminate duplicate common styles between your variants.
I am using css modules for my project, and I have a file positioning.css which has some useful classes that I want to import. e.g. .right, .left
What is the best approach for this using CSS Modules?
At the moment I can see 2 options, but they are not all that great:
composition in the component's style
.right {
composes: right from '../styles/positioning.css';
}
or
multiple css module imports in the component
import positioning from '../styles/positioning.css'
import styles from './myComponent.css';
Object.assign(styles, positioning)
class Menu extends Component {
render() {
return (
<div styleName='menu'>
<div styleName='left'>this is left</div>
<div styleName='right'>this is right</div>
</div>
);
}
};
export default CSSModules(Menu, styles);
I have manage to get this working:
// css file
#value class-to-compose from "file-where-class-is-defined.css";
.someclass {
composes: class-to-compose;
// other styles
}
One approach is to collect all app level css variables and calculations at the top level into app.css
#import "./theme/layout.css";
#import "./theme/colors.css";
...
Then reference app.css using
#import "../../app.css";
This way you can manage #import scope inside one file at the root level.
I'll go with the first proposition. (the result is quiet the same)
both proposition have quiet the same result
If someday you have to edit your Menu css, you'll just have to edit your Menu css and not your component.
You let CSSModules take decisions. (more futur proof?)
You could import the css files that you use frequently into a broader CSS file that you import on specific pages, this is taking the second approach but making it cleaner, especially if you have a lot of common core css files that you import on pretty much all pages.
I would advise you to go with [Sass] [1]. Sass allows for the usage of partials (i.e. distributed / scoped css sheets).
You write scoped (to the components you want) css and import all your partials into your main.css then.
Couple of other advantages:
you can do theming by having one partial that defines your them via variables, which you import first and then all your partials can use these variables.
having the css on a scoped level (at least to me) felt more "reactish" where components are supposed to be stand alone, but it also wasn't inline styling, which I find ugly and weird (I don't like to clutter down my .js files with styles)
[1] http://sass-lang.com/
I find this one line very helpful with importing:
#import 'file.css';
You could set these as globals and update their names to be a tad more semantic, like BootStraps pull-right.
If you declare them as
:global(.right) {
/* ... */
}
You can then just use them in your app by preferably importing globals early on in the entry point.
You should take a look at the option by vue.js component (scoped/overall)
You can choose a precompile css language like SASS, which can use #extend ...etc to reuse the common property, like below:
%common {
width: 100%;
height: inherit;
}
.my-class {
#extend %common;
}