I am recently getting started with Next JS. for styling, I am using Material UI. one issue I am facing is with the fonts. I couldn't able to change the font family to a different font. as per the below example (Github link), I created a _document.js page inside my pages folder
https://github.com/mui-org/material-ui/tree/master/examples/nextjs/pages
_document.js
in the below code I tried changing Roboto with Quicksand
import React from "react";
import Document, { Html, Head, Main, NextScript } from "next/document";
import { ServerStyleSheets } from "#material-ui/core/styles";
// import theme from "../components/Theme.js";
export default class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head>
{/* PWA primary color */}
{/* <meta name="theme-color" content={theme.palette.primary.main} /> */}
<link
rel="stylesheet"
// href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
href="https://fonts.googleapis.com/css?family=Quicksand:300,400,500,700&display=swap"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
// `getInitialProps` belongs to `_document` (instead of `_app`),
// it's compatible with server-side generation (SSG).
MyDocument.getInitialProps = async (ctx) => {
const sheets = new ServerStyleSheets();
const originalRenderPage = ctx.renderPage;
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
// Styles fragment is rendered after the app and page rendering finish.
styles: [
...React.Children.toArray(initialProps.styles),
sheets.getStyleElement(),
],
};
};
Also, I customized my MUI theme object as below
const theme = createMuiTheme({
palette: {
type: "dark",
background: {
default: "#212121",
},
typography: {
fontFamily: "Quicksand",
},
},
});
Layout.js
import { createMuiTheme } from "#material-ui/core/styles";
import { ThemeProvider } from "#material-ui/styles";
import { makeStyles } from "#material-ui/core/styles";
import CssBaseline from "#material-ui/core/CssBaseline";
import Container from "#material-ui/core/Container";
import Divider from "#material-ui/core/Divider";
import NavBar from "./NavBar.js";
import Footer from "./Footer.js";
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
flexDirection: "column",
minHeight: "100vh",
},
main: {
marginTop: theme.spacing(0),
marginBottom: theme.spacing(10),
},
}));
const theme = createMuiTheme({
palette: {
type: "dark",
background: {
default: "#212121",
},
typography: {
fontFamily: "Quicksand",
},
},
});
function Layout({ children }) {
const classes = useStyles();
return (
<div className={classes.root}>
<ThemeProvider theme={theme}>
<CssBaseline />
<Container component="main" className={classes.main} maxWidth="md">
<NavBar />
<Divider />
{children}
<Divider />
<Footer />
</Container>
</ThemeProvider>
</div>
);
}
export default Layout;
But no luck. can anyone please advise?
Thanks In Advance
Venk
You can wrap your in _app.js with a ThemeProvider.
Example _app.js:
import '../styles/globals.scss'
import { motion, AnimatePresence } from 'framer-motion'
import { useRouter } from 'next/router'
import Header from '../components/Header'
import { AuthProvider } from '../contexts/AuthContext'
import { CartProvider } from '../contexts/CartContext'
import { ThemeProvider } from '#material-ui/core'
import theme from '../styles/theme'
export default function App({ Component, pageProps }) {
const router = useRouter()
return(
<AnimatePresence exitBeforeEnter>
<CartProvider>
<AuthProvider>
<ThemeProvider theme={theme}>
<Header />
<motion.div key={router.pathname} className="main">
<Component { ...pageProps } />
</motion.div>
</ThemeProvider>
</AuthProvider>
</CartProvider>
</AnimatePresence>
)
}
Example theme file:
import { createTheme, responsiveFontSizes } from "#material-ui/core"
let theme = createTheme({
palette: {
primary: {
main: '#0277bd',
},
secondary: {
main: '#b2ff59',
},
darkGray: {
main: '#333333'
},
},
})
theme = responsiveFontSizes(theme)
export default theme
I'm not sure how you understand the Mui theme concept. Let's just talk about NextJs first.
NextJs is no different from React. To set the global font-family you just have to put font-family: your desired font into the html, body {} block inside the global.css which is already imported at the top level of _app.js. Every component will use that global font unless it has custom styles. Example:
html, body {
padding: 0;
margin: 0;
font-family: Quicksand, cursive;
}
Now, with Mui, components use a designed style system which conclude default font option as well. To inject your custom style into every single Mui's component you need to wrap all of them inside a top/root level <ThemeProvider theme={yourCutomTheme}> component to override the defaults, reference: Custom Theming from Mui official doc.
Things are not that complicated tho.
In Nextjs V10+ with material-ui
_document.js
Don't forget to add this link tag before your fonts for improving the performance
<link rel="preconnect" href="https://fonts.gstatic.com" />
_app.js
add the override object to make your font a global font in your app instead of Roboto the default
const theme = responsiveFontSizes(
createMuiTheme({
// your custom UI Config ,
typography: {
fontFamily: "Quicksand",
},
overrides: {
MuiCssBaseline: {
"#global": {
"#font-face": [
{
fontFamily: "Quicksand",
fontStyle: "normal",
fontDisplay: "swap",
fontWeight: // your font weight,
},
],
},
},
},
})
)
Here is what worked for me with Google Fonts, MUI v5, NextJs v13 (TS)
https://nextjs.org/docs/api-reference/next/font
https://mui.com/material-ui/customization/typography/#adding-amp-disabling-variants
Add primary and secondary variant to theme
import { Barlow, Cormorant } from "#next/font/google";
export const barlow = Barlow({
weight: ["200", "400", "500", "600", "700"],
display: "swap",
fallback: ["Helvetica", "Arial", "sans-serif"],
});
export const cormorant = Cormorant({
weight: ["300", "400", "500", "600", "700"],
display: "swap",
fallback: ["Times New Roman", "Times", "serif"],
});
declare module "#mui/material/styles" {
interface TypographyVariants {
primary: React.CSSProperties;
secondary: React.CSSProperties;
}
interface TypographyVariantsOptions {
primary?: React.CSSProperties;
secondary?: React.CSSProperties;
}
}
declare module "#mui/material/Typography" {
interface TypographyPropsVariantOverrides {
primary: true;
secondary: true;
}
}
let theme = createTheme({
typography: {
fontFamily: barlow.style.fontFamily,
body1: {
fontFamily: barlow.style.fontFamily,
},
primary: {
fontFamily: barlow.style.fontFamily,
},
secondary: {
fontFamily: cormorant.style.fontFamily,
},
},
});
in component as sx prop
<Typography
sx={{
fontFamily: theme.typography.secondary.fontFamily,
}}
>
More About Us:
</Typography>
cormorant overides the default
or props
<Typography
variant="secondary"
>
More About Us:
</Typography>
I have various resource components that sit inside the react-admin Admin component. The admin component has a custom layout. Each of the resource screens has a different primary colour though, to represent the section that you are in and the content type. How is it possible to pass a specific theme object variable of primary colour to each resource that is in then used inside my custom Layout?
<Admin
authProvider={AuthProvider}
dashboard={Dashboard}
dataProvider={graphQLProvider}
history={history}
layout={Layout}
title="Home"
<Resource name="User" options={{label: 'Administrators'}} list={UserList} show={UserShow} edit={UserEdit}
create={UserCreate} icon={UserIcon}/>
Should I be using the className prop which is available on 'every react-admin component' or is there a better way?
React-admin doesn't support this feature, but you can achieve it using a custom layout (see Replacing the appbar) and a listener on the history location to get the resource from the URL. Something like:
import React, { useEffect, useState } from 'react';
import { useHistory } from 'react-router-dom';
import { AppBar } from 'react-admin';
import { makeStyles } from '#material-ui/core/styles';
const useStyles = makeStyles({
posts: {
backgroundColor: 'red',
},
comments: {
backgroundColor: 'blue',
}
});
const MyAppBar = ({ children }) => {
const [resource, setResource] = useState();
const history = useHistory();
const classes = useStyles();
useEffect(() => {
const newResource = extractResourceFromPathname(history.location.pathname); // left as an exercise to the reader
if (newResource !== resource) {
setResource(newResource);
}
}, [history.location.pathname]);
return <AppBar className={classes[resource]} {...props} />
}
I have a header and want to style all of it's buttons differently than my global theme. I have tried using a child theme like:
<ThemeProvider
theme={(outerTheme) =>
_.merge(outerTheme, {
overrides: {
MuiButton: {
label: {
color: "#fff",
},
},
},
})
}
>
However, while I had expected this to override only MuiButton's in the child theme, it overrode them in them globally.
I know I can use makeStyles but, then, as far as I know, I have to reference it in all the child components which want to use the style. I'd like to wrap a higher level component and have all child components pick up the style. How is this done?
You can do this:
import Button from '#material-ui/core/Button';
import { purple } from '#material-ui/core/colors';
import { makeStyles } from '#material-ui/core/styles';
const useStyles = makeStyles((theme) => ({
header: {
"& button": {
marginRight: theme.spacing(1),
color: theme.palette.getContrastText(purple[500]),
backgroundColor: purple[500],
"&:hover": {
backgroundColor: purple[700],
},
},
},
});
function CustomHeader(props) {
const classes = useStyles();
return (
<div className={classes.header}>
<Button>Button 1</Button>
<Button>Button 2</Button>
<Button disabled>Button 3</Button>
</div>
)
};
I am working in a codebase where over time, we have done many changes to our pages and components thus resulting in a lot of styles that are now not being used. We would like to clean up our codebase and remove any unused makeStyles properties.
Our styles are being imported from a separate file. For example:
container.styles.ts
import { makeStyles } from "#material-ui/core";
export const useStyles = makeStyles({
container: {
backgroundColor: "white",
...
},
oldContainerStyle1: { ... },
oldContainerStyle2: { ... },
oldContainerStyle3: { ... },
oldComponentStyle1: { ... },
...
},
});
container.tsx
import React, { FC } from "react";
import { useStyles } from "./container.styles";
interface IProps {
...
}
export const AutoCompleteContainer: FC<IProps> = ({...}) => {
const classes = useStyles();
return (
<div className={classes.container}>
...
)
}
We are using tslint and have looked into seeing if it is possible but we still cannot seem to find an answer on this issue.
Install this dependency https://github.com/jens-ox/eslint-plugin-material-ui-unused-classes
not sure for tslint.
Thinking of the concept of a SASS mixin, what would be the best approach to enable the re-use of style blocks that incorporate the current theme.
For example I have two components that import their respective styles:
Component 1
index
styles
Component 2
index
styles
Each component has a text block
Component 1
import { styles } from './styles'
....
<div className={classes.richText}>...</div>
Component 2
import { styles } from './styles'
....
<div className={classes.richText}>...</div>
The styles for richText are the same but I would rather not duplicate them in the imported styles file.
I would rather have a single file that exposes reusable CSS properties based on the theme.
The only way I can currently do this is by returning an object that I have passed the theme to e.g.
const RichText = (theme) => {
return {
fontWeight: 100,
color: theme.typography.body1.color
}
}
Then import this into the styles
Component 1
styles.js
import { RichText } from '../mixins/'
const styles = theme => ({
richText: {
...RichText(theme),
fontSize: '1rem'
}
Component 2
styles.js
import { RichText } from '../mixins/'
const styles = theme => ({
richText: {
...RichText(theme),
fontSize: '1.2rem'
}
Feels like there has to be a better way: utilising withTheme() maybe?
Let's say you want to custom a checkbox as below:
import { makeStyles } from '#material-ui/core/styles';
import CheckBox from '#material-ui/core/CheckBox';
const useStyles = makeStyles((theme) => ({
root: {
color: theme.status.danger,
'&$checked': {
color: theme.status.danger,
},
},
checked: {},
}));
function CustomCheckbox() {
const classes = useStyles();
return (
<Checkbox
defaultChecked
classes={{
root: classes.root,
checked: classes.checked,
}}
/>
);
}
And you want to reuse its style as many components as you can.
You have two options: ThemeProvider or export your custom style.
Changing the component's theme
Themes let you apply a consistent tone to your app. It allows you to customize all design aspects of your project in order to meet the specific needs of your business or brand.
import { createMuiTheme, ThemeProvider } from '#material-ui/core/styles';
import orange from '#material-ui/core/colors/orange';
const myTheme = createMuiTheme({
status: {
danger: orange[500],
},
});
export default function CustomStyles() {
return (
<ThemeProvider theme={myTheme}>
<CustomCheckbox />
</ThemeProvider>
);
}
Exporting your custom styles
Separate your components in a proper folder, i.e: ./assets/styles:
import { makeStyles } from '#material-ui/core/styles';
export const useCheckBoxStyles = makeStyles((theme) => ({
root: {
color: theme.status.danger,
'&$checked': {
color: theme.status.danger,
},
},
checked: {},
}));
And your components tree ./components:
import { useCheckBoxStyles } from './assets/styles';
function CustomCheckbox() {
const classes = useCheckBoxStyles();
return (
<Checkbox
defaultChecked
classes={{
root: classes.root,
checked: classes.checked,
}}
/>
);
}
References
https://material-ui.com/customization/theming