Introduction
How you will make this with css?
You are not able to make it theoretically and practically with css.
This is one of the all reasons why CSS sucks versus JSS.
For sure, JSS have more functionality with React, but who in 2021 are using vanilla JS?
About JSS
JSS is an authoring tool for CSS which allows you to use JavaScript to describe styles in a declarative, conflict-free and reusable way. It can compile in the browser, server-side or at build time in Node.
JSS is framework agnostic. It consists of multiple packages: the core, plugins, framework integrations and others.
JSS features
- Real CSS.
- Collision-free selectors.
- Code reuse.
- Ease of removal and modification.
- Dynamic styles.
- User-controlled animations.
- Critical CSS.
- Plugins.
- Expressive syntax.
- Full isolation.
- React integration.
- JavaScript build pipeline.
Small project as example
Setuping Environment
Stack: Nextjs, Typescript, Jss
yarn create next-app --typescript
yarn add react-jss jss-plugin-template jss-plugin-global jss-plugin-nested jss-plugin-camel-case jss-plugin-default-unit jss-plugin-compose
Of course we need add JSS and some plugins.
So create file pages/_document.tsx
(to set up ssr)
import React from 'react';
import Document, { Html, Head, Main, NextScript } from 'next/document';
import { SheetsRegistry, JssProvider, createGenerateId } from 'react-jss';
export default class MyDocument extends Document {
render() {
return (
<Html lang={'en'}>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
MyDocument.getInitialProps = async (ctx) => {
const registry = new SheetsRegistry();
const generateId = createGenerateId();
const originalRenderPage = ctx.renderPage;
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) =>
(
<JssProvider registry={registry} generateId={generateId}>
<App {...props} />
</JssProvider>
),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
<style id={'server-side-styles'}>{registry.toString()}</style>
</>
),
};
};
All what we do here its adding necessary JSS SheetsRegisty to our default ctx.renderPage
manually so that the whole react tree gets the required stylesheet
.
The following snippet shows the available option we can use on ctx.renderPage
.
After that delete folder styles
because now ̶y̶o̶u̶r̶ ̶l̶i̶f̶e̶ ̶b̶e̶c̶o̶m̶e̶s̶ ̶b̶e̶ ̶b̶e̶t̶t̶e̶r̶ we will not be need css more.
In pages/_app.tsx
(this story just about example of jss, in real life dont use this archetecture, instead use state management util
and split up your providers in different Layouts (you can read another story about some structure moments link ))
import type { AppProps } from 'next/app';
import { useState } from 'react';
import { ThemeProvider } from 'react-jss';
const _App = ({ Component, pageProps }: AppProps) => {
const initialTheme = {
background: '#222222',
text: '#e7f1fe',
};
const [theme, setTheme] = useState(initialTheme);
return (
<ThemeProvider theme={theme}>
<Component {...pageProps} setTheme={setTheme} />
</ThemeProvider>
);
};
export default _App;
So here we wrapping <Component {...pageProps} setTheme={setTheme}/>
with <ThemeProvider theme={theme}>
and upper we initializate with hook useState [theme, setTheme]
so then we need move to file pages/index.tsx
With ts, as we will receive props in index.tsx
we need write type
which will be describes which props we will receive
type ThemeType = { [Property in 'background' | 'text']: string };
type AppPropsType = {
setTheme: Dispatch<SetStateAction<{ThemeType>>
};
and here we adding ThemeType
.
Finally lets try to add styling with JSS, for do it we need
const useStyles = createUseStyles(({ background, text }: ThemeType) => ({}));
so in first param we can get access of our theme properties and for better code lets give type for this params.
Then as returning value we will write styling code,
as we added jss-plugin-global
we have opportunity to change gloabal styling, as example lets nullity default browser styles, to do that in returning object we need add key '@global'
with value { body: {padding: 0,margin: 0,},},
as least in result we should have
const useStyles = createUseStyles(({ background, text }: ThemeType) => ({
'@global': {
body: {
padding: 0,
margin: 0,
},
},
}));
then, lets add some class
container: {
background,
color: text,
width: '100vw',
height: '100vh',
font: { family: 'monospace', size: 20 },
},
as you see we don't need to write fonFamily or fontSize,
we can easyly structurate it with object with key font
.
Then, in body of App
component, we will use our useStyles
by
const { container } = useStyles();
return <div className={container}>App</div>;
and all code of this file
import { SetStateAction } from 'react';
import { Dispatch, FC } from 'react';
import { createUseStyles } from 'react-jss';
type ThemeType = { [Property in 'background' | 'text']: string };
type AppPropsType = { setTheme: Dispatch<SetStateAction<ThemeType>> };
const useStyles = createUseStyles(({ background, text }: ThemeType) => ({
'@global': {
body: {
padding: 0,
margin: 0,
},
},
container: {
background,
color: text,
width: '100vw',
height: '100vh',
font: { family: 'monospace', size: 20 },
},
}));
const App: FC<AppPropsType> = () => {
const { container } = useStyles();
return <div className={container}>App</div>;
};
export default App;
Finally lets test this part by command
yarn dev
as we setup our theme, we should have (dark background, and white text color)
For sure, u easyly can make this with css, yeah, so right now will be advanced features
we can create
const CENTERED_FLEX_CONTAINER = 'centered-flex-container'
so then we can use it as
[CENTERED_FLEX_CONTAINER]: {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
and then due to we added plugin jss-plugin-compose
we can use in
container: {
composes: [`$${CENTERED_FLEX_CONTAINER}`],
//other code
},
For see killer-feature
we need to create function which will generate random color, function:
const toGetRandomColor = () => `#${Math.random().toString(16).substr(-6)}`;
and with hook useEffect make inverval function which in every iteration set new color
const [color, setColor] = useState(theme.text);
useEffect(() => {
const interval = setInterval(() => {
setColor(toGetRandomColor());
}, 420);
return () => clearInterval(interval);
}, []);
then we need paste our random color to useStyles
const { container } = useStyles({ color } as any);
and in useStyles add new class
colorContainer: ({ color }: any) => ({ color })
so at least in every 0.42 sec we will see updated class, so in dev tools you can see that its not an inline styling, class dynamicly changes value, that is absolute awesome, so then for test that our theme can be dynamiclty changed lets get the theme
we can easyly do this with useTheme
hook
After than, we need an array with theme keys
so:
const themeKeysArr = Object.keys(theme) as (keyof ThemeType)[];
so then in jsx lets add simple inputs construction
{themeKeysArr.map((name) => {
return (
<input value={theme[name]} placeholder={name.toUpperCase()} onChange={onChange} name={name} key={name} />
);
})}
after that lets add some styling to inputs
inputsContainer: {
margin: [8, 0, 0, 0],
padding: 10,
'& input': {
outline: 'none',
border: '1px solid',
borderRadius: 8,
padding: [6, 8],
margin: [0, 4],
color: text,
background: 'transparent',
},
},
In JSS &
have the same logic as have in Sass, also using [8, 0, 0, 0]
we can setup (marginTop - 8, margin(right, vbottom, left) is equal to zero).
Then lets add class container
with this styling:
contentContainer: {
composes: [`$${CENTERED_FLEX_CONTAINER}`],
flex: { direction: 'column' },
}
Finally lets update our jsx
part with this code:
<div className={`${container} ${colorContainer}`}>
<div className={contentContainer}>
<div>STOP USE CSS, USE JSS INSTEAD.</div>
<div className={inputsContainer}>
{themeKeysArr.map((name) => {
return (
<input value={theme[name]} placeholder={name.toUpperCase()} onChange={onChange} name={name} key={name} />
);
})}
</div>
</div>
</div>
for sure we need destructurate other classes with: const { container, contentContainer, inputsContainer, colorContainer } = useStyles({ color } as any);
and to add multi classes we need use (ES6 syntax) at least we should have something like that:
And final code:
import { ChangeEventHandler, SetStateAction, useEffect, useState } from 'react';
import { Dispatch, FC } from 'react';
import { createUseStyles, useTheme } from 'react-jss';
type ThemeType = { [Property in 'background' | 'text']: string };
type AppPropsType = { setTheme: Dispatch<SetStateAction<ThemeType>> };
const CENTERED_FLEX_CONTAINER = 'centered-flex-container';
const useStyles = createUseStyles(({ background, text }: ThemeType) => ({
'@global': {
body: {
padding: 0,
margin: 0,
},
},
[CENTERED_FLEX_CONTAINER]: {
display: 'flex', <div className={`${container} ${colorContainer}`}>
<div className={contentContainer}>
<div>STOP USE CSS, USE JSS INSTEAD.</div>
<div className={inputsContainer}>
{themeKeysArr.map((name) => {
return (
<input value={theme[name]} placeholder={name.toUpperCase()} onChange={onChange} name={name} key={name} />
);
})}
</div>
</div>
</div>
alignItems: 'center',
justifyContent: 'center',
},
container: {
composes: `$${CENTERED_FLEX_CONTAINER}`,
background,
font: { family: 'monospace', size: 20 },
width: '100vw',
height: '100vh',
},
colorContainer: ({ color }: any) => ({ color }),
contentContainer: {
composes: [`$${CENTERED_FLEX_CONTAINER}`],
flex: { direction: 'column' },
},
inputsContainer: {
margin: [8, 0, 0, 0],
padding: 10,
'& input': {
outline: 'none',
border: '1px solid',
borderRadius: 8,
padding: [6, 8],
margin: [0, 4],
color: text,
background: 'transparent',
},
},
}));
const App: FC<AppPropsType> = ({ setTheme }) => {
const theme = useTheme<ThemeType>();
const [color, setColor] = useState(theme.text);
const toGetRandomColor = () => `#${Math.random().toString(16).substr(-6)}`;
useEffect(() => {
const interval = setInterval(() => {
setColor(toGetRandomColor());
}, 420);
return () => clearInterval(interval);
}, []);
const { container, contentContainer, inputsContainer, colorContainer } = useStyles({ color } as any);
const onChange: ChangeEventHandler<HTMLInputElement> = ({ target: { value, name } }) => {
setTheme((state) => ({ ...state, [name]: value }));
};
const themeKeysArr = Object.keys(theme) as (keyof ThemeType)[];
return (
<div className={`${container} ${colorContainer}`}>
<div className={contentContainer}>
<div>STOP USE CSS, USE JSS INSTEAD.</div>
<div className={inputsContainer}>
{themeKeysArr.map((name) => {
return (
<input value={theme[name]} placeholder={name.toUpperCase()} onChange={onChange} name={name} key={name} />
);
})}
</div>
</div>
</div>
);
};
export default App;
Conclusion
Its just a small part of all features of jss, but this small example can give you big opportunity and understating of jss.
Thanks for reading, I so appreciate this ♥.
Top comments (2)
this is cool but. i could do the same thing with less code using web components and template css with no perf hit.
Cool article, but terrible title.
If I wanted to create limitations to my styling and overcomplicate my app. I'll use JSS.