✨ Like JSS but optimized for TypeScript. Powered by emotion ✨
'tss-react' is intended to be a replacement for 'react-jss' and for
@material-ui v4 makeStyle.
It's API is focused on providing maximum type safety and minimum verbosity.
This module is a tinny extension for @emotion/react.
- ✅ As fast as
emotion(see the difference with mui'smakeStyles) - ✅ As lightweight as
@emotion/react. - ✅ Server side rendering support (e.g: Next.js).
- ✅ Seamless integration with material-ui v5.
Perfect for those who don't like the switch from the Hook API to the Styled API in v5. - ✅ Complete
@emotioncustom cache integration.
$ yarn add tss-react @emotion/react./makeStyles.ts
import { createMakeStyles } from "tss-react";
function useTheme() {
return {
"primaryColor": "#32CD32",
};
}
// material-ui users can pass in useTheme imported like: import { useTheme } from "@material-ui/core/styles";
export const { makeStyles } = createMakeStyles({ useTheme });./MyComponent.tsx
import { makeStyles } from "./makeStyles";
const useStyles = makeStyles<{ color: "red" | "blue" }>()(
(theme, { color }) => ({
"root": {
color,
"&:hover": {
"backgroundColor": theme.primaryColor,
},
},
}),
);
export function MyComponent(props: Props) {
const { className } = props;
const [color, setColor] = useState<"red" | "blue">("red");
const { classes, cx } = useStyles({ color });
return <span className={cx(classes.root, className)}>hello world</span>;
}Material-UI users only: Setup injection priority.
Click to expand instructions for material-ui v4.
import { render } from "react-dom";
import { StylesProvider } from "@material-ui/core/styles";
render(
<StylesProvider injectFirst>
<Root />
</StylesProvider>,
document.getElementById("root"),
);If you need SSR You can find here a Next.js setup to use as reference.
Click to expand instructions for material-ui v5
Don't use <StyledEngineProvider injectFirst/> but do this instead:
import { render } from "react-dom";
import { CacheProvider } from "@emotion/react";
import { getCache } from "tss-react/cache";
render(
<CacheProvider value={getCache()}>
<Root />
</StyledEngineProvider>,
document.getElementById("root"),
);Feel free to use any emotion cache you want.
You don't have to use the default one provided in tss-react/cache.
NOTE:
If you don't want to end up writing things like:
import { makeStyles } from "../../../../../../makeStyles";You can put "baseUrl": "src" in
your tsconfig.json and import things relative yo your src/ directory.
import {
createMakeStyles, //<- Create an instance of makeStyle() for your theme.
keyframe, //<- The function as defined in @emotion/react and @emotion/css
GlobalStyles, //<- A component to define global styles.
} from "tss-react";Your component style may depend on the props and state of the components:
const useStyles = makeStyles<{ color: string }>()((_theme, { color }) => ({
"root": {
"backgroundColor": color,
},
}));
//...
const { classes } = useStyles({ "color": "grey" });...Or it may not:
const useStyles = makeStyles()({
//If you don't need neither the theme nor any state or
//props to describe your component style you can pass-in
//an object instead of a callback.
"root": {
"backgroundColor": "pink",
},
});
//...
const { classes } = useStyles();Beside the classes, useStyles also returns cx, css and your theme.
css is the function as defined in @emotion/css
cx is the function as defined in @emotion/css
const { classes, cx, css, theme } = useStyles(/*...*/);In some components you may need cx, css or theme without defining
custom classes.
For that purpose you can use the useStyles hook returned
by createMakeStyles.
makeStyles.ts
import { createMakeStyles } from "tss-react";
function useTheme() {
return {
"primaryColor": "#32CD32",
};
}
export const {
makeStyles,
useStyles, //<- This useStyles is like the useStyles you get when you
// call makeStyles but it doesn't return a classes object.
} = createMakeStyles({ useTheme });./MyComponent.tsx
//Here we ca import useStyles directly instead of generating it from makeStyles.
import { useStyles } from "./makeStyles";
export function MyComponent(props: Props) {
const { className } = props;
const { cx, css, theme } = useStyles();
return (
<span className={cx(css({ "color": theme.primaryColor }), className)}>
hello world
</span>
);
}Sometimes you might want to insert global css.
You can use the <GlobalStyles /> component to do this.
It's styles (with an s) prop should be of same type as the css() function
argument.
import { GlobalStyles } from "tss-react";
function MyComponent() {
return (
<>
<GlobalStyles
styles={{
"body": {
"backgroundColor": "pink",
},
".foo": {
"color": "cyan",
},
}}
/>
<h1 className="foo">This text will be cyan</h1>
</>
);
}// Reexport from @emotion/react
import { keyframe } from "tss-react";
import { makeStyles } from "./makeStyles";
export const useStyles = makeStyles()({
"svg": {
"& g": {
"opacity": 0,
"animation": `${keyframes`
60%, 100% {
opacity: 0;
}
0% {
opacity: 0;
}
40% {
opacity: 1;
}
`} 3.5s infinite ease-in-out`,
},
},
});If you are using custom emotion cache tss-react will transparently
pick up the cache you have provided using <CacheProvider /> from @emotion/react.
import { CacheProvider } from "@emotion/react";
import createCache from "@emotion/cache";
/* OR:
import createCache from "tss-react/@emotion/cache";
*/
const myCache = createCache({
"key": "my-prefix-key",
//...
});
render(<CacheProvider value={myCache}>{/* ... */}</CacheProvider>);You can also opt for telling tss-react to use a specific cache and ignore
the cache provided by the <CacheProvider />.
import { createMakeStyles } from "tss-react";
import createCache from "@emotion/cache";
const { makeStyles } = createMakeStyles({
useTheme,
"cache": createCache({ "key": "my-prefix-key" }),
});If there is no cache provided by <CacheProvider /> nor any cache specified
when calling createMakeStyles() then the cache used is import { getCache } from "tss-react/cache".
tss-react unlike jss-react doesn't support the $ syntax,
but you'll see. It isn't needed.
When you want to reuse style within the same component.
import { makeStyles } from "./makeStyles";
import type { CSSObject } from "tss-react";
const useStyles = makeStyles<{ n: number; color: string }>()(
(theme, { n, color }) => {
const root: CSSObject = {
"color": theme.primaryColor,
"border": `${n}px solid black`,
};
return {
root,
"foo": {
...root,
//Style specific to foo
color,
},
};
},
);MyComponent.tsx
import { makeStyles } from "./makeStyles";
// You can always define the Theme type as: "export type Theme = ReturnType<typeof useTheme>;"
import type { Theme } from "./makeStyles";
import type { CSSObject } from "tss-react";
//Can be used in another component
export const getRootStyle = (
theme: Theme,
params: { n: number },
): CSSObject => ({
"color": theme.primaryColor,
"border": `${params.n}px solid black`,
});
const useStyles = makeStyles<
Parameters<typeof getRootStyle>[1] & { color: string }
>()((theme, { n, color }) => ({
"root": getRootStyle(theme, { n }),
// Other styles...
}));There are some minimal configuration required to make tss-react
work with SSR.
The following instructions are assuming you are using tss-react standalone
or alongside @material-ui v5. You can find here
a Next.js setup with @material-ui v4.
With Next.js
Just create a file page/_document.tsx as follow:
import { createDocument } from "tss-react/nextJs";
const { Document } = createDocument();
/*
If you use custom cache you should provide it here:
const { Document } = createDocument({ "caches": [ cache1, cache2, ... ] });
*/
export default Document;import Document from "next/document";
import type { DocumentContext } from "next/document";
import { createGetInitialProps } from "tss-react/nextJs";
const { getInitialProps } = createGetInitialProps();
/*
If you use custom cache you should provide it here:
const { getInitialProps } = createGetInitialProps({ "caches": [ cache1, cache2, ... ] });
*/
export default class AppDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
return getInitialProps(ctx);
}
//...Rest of your class...
}import Document from "next/document";
import type { DocumentContext } from "next/document";
import { createPageHtmlToStyleTags } from "tss-react/nextJs";
const { pageHtmlToStyleTags } = createPageHtmlToStyleTags();
/*
If you use custom cache you should provide it here:
const { pageHtmlToStyleTags } = createPageHtmlToStyleTags({ "caches": [ cache1, cache2, ... ] });
*/
export default class AppDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
const page = await ctx.renderPage();
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
"styles": (
<>
{initialProps.styles}
{pageHtmlToStyleTags({ "pageHtml": page.html })}
</>
),
};
}
//...Rest of your class...
}yarn add @emotion/serverimport { renderToString } from "react-dom/server";
import createEmotionServer from "@emotion/server/create-instance";
import { getCache } from "tss-react/cache";
import { createMakeStyles } from "tss-react";
const emotionServers = [
getCache(), //If you use custom cache(s) provide it/them here instead of the default.
].map(createEmotionServer);
const element = <App />;
const html = renderToString(element);
res.status(200).header("Content-Type", "text/html").send(`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>My site</title>
${emotionServers
.map(({ extractCriticalToChunks, constructStyleTagsFromChunks }) =>
constructStyleTagsFromChunks(extractCriticalToChunks(html)),
)
.join("")}
</head>
<body>
<div id="root">${html}</div>
<script src="./bundle.js"></script>
</body>
</html>`);yarn
yarn build
#For automatically recompiling when file change
#npx tsc -w
# To start the Single Page Application test app (create react app)
yarn start_spa
# To start the Server Side Rendering app (next.js)
yarn start_ssr
# To start the Server Side Rendering app that test the mui v4 integration.
yarn start_muiV4In SSR everything should work with JavaScript disabled
Click to expand
Why this instead of the hook API of Material UI v4?
First of all because makeStyle is deprecated in @material-ui v5 but also
because it has some major flaws. Let's consider this example:
import { makeStyles, createStyles } from "@material-ui/core/styles";
type Props = {
color: "red" | "blue";
};
const useStyles = makeStyles(theme =>
createStyles<"root" | "label", { color: "red" | "blue" }>({
"root": {
"backgroundColor": theme.palette.primary.main,
},
"label": ({ color }) => ({
color,
}),
}),
);
function MyComponent(props: Props) {
const classes = useStyles(props);
return (
<div className={classes.root}>
<span className={classes.label}>Hello World</span>
</div>
);
}Two pain points:
- Because TypeScript doesn't support partial argument inference,
we have to explicitly enumerate the classes name as an union type
"root" | "label". - We shouldn't have to import
createStylesto get correct typings.
Let's now compare with tss-react
import { makeStyles } from "./makeStyles";
type Props = {
color: "red" | "blue";
};
const { useStyles } = makeStyles<{ color: "red" | "blue" }>()(
(theme, { color }) => ({
"root": {
"backgroundColor": theme.palette.primary.main,
},
"label": { color },
}),
);
function MyComponent(props: Props) {
const { classes } = useStyles(props);
return (
<div className={classes.root}>
<span className={classes.label}>Hello World</span>
</div>
);
}Benefits:
- Less verbose, same type safety.
- You don't need to remember how things are supposed to be named, just let intellisense guide you.
Besides, the hook api of material-ui, have other problems:
See this issue

