Skip to content

Introductory tutorial

Learn how to import and style Joy UI components to build a simple login page with light and dark modes.

This tutorial will walk you through how to assemble the UI for a basic login page using Joy UI. You'll be introduced to several common components as well as some of the props you can use to control their styles. You'll also encounter key features of Joy UI such as global variants, the sx prop, and the useColorScheme hook.

By the end, you should understand how to:

  1. import and customize Joy UI components
  2. add styles to Joy UI components with sx
  3. override default HTML elements with component
  4. toggle light and dark mode with useColorScheme

Interactive demo

Here's what the final product looks like—click on the <> icon underneath the demo to see the full source code:

Welcome!

Sign in to continue.

Don't have an account?Sign up

Prerequisites

This tutorial assumes that you've already:

Import the Sheet component for structure

The Sheet component is a <div> container that supports Joy UI's global variants feature, helping to ensure consistency across your app.

Import Sheet and add it to your app as shown below. (If you're using Create React App, for example, all of this code should go in App.js.) Notice that Joy UI components must be nested within <CssVarsProvider />:

import * as React from 'react';
import { CssVarsProvider } from '@mui/joy/styles';
import Sheet from '@mui/joy/Sheet';

export default function App() {
  return (
    <CssVarsProvider>
      <Sheet variant="outlined">Welcome!</Sheet>
    </CssVarsProvider>
  );
}

Add styles with the sx prop

All Joy UI components accept the sx prop, which gives you access to a shorthand syntax for writing CSS. It's great for creating one-off customizations or rapidly experimenting with different styles.

Replace your basic Sheet from the previous step with the following sx-styled Sheet:

<Sheet
  sx={{
    width: 300,
    mx: 'auto', // margin left & right
    my: 4, // margin top & botom
    py: 3, // padding top & bottom
    px: 2, // padding left & right
    display: 'flex',
    flexDirection: 'column',
    gap: 2,
    borderRadius: 'sm',
    boxShadow: 'md',
  }}
>
  Welcome!
</Sheet>

Add text with the Typography component

The Typography component replaces HTML header, paragraph, and span tags to help maintain a consistent hierarchy of text on the page.

Add an import for Typography with the rest of your imports:

import Typography from '@mui/joy/Typography';

Replace Welcome! inside your Sheet component with this <div>:

<div>
  <Typography level="h4" component="h1">
    Welcome!
  </Typography>
  <Typography level="body2">Sign in to continue.</Typography>
</div>

Add Text Field for user inputs

The Text Field component bundles together the Form Control, Form Label, Input, and Form Helper Text components to provide you with a sophisticated field for user input.

Add an import for Text Field with the rest of your imports:

import TextField from '@mui/joy/TextField';

Insert these two Text Fields below the <div> from the previous step, inside the Sheet:

<TextField
  // html input attribute
  name="email"
  type="email"
  placeholder="johndoe@email.com"
  // pass down to FormLabel as children
  label="Email"
/>
<TextField
  name="password"
  type="password"
  placeholder="password"
  label="Password"
/>

The Button and Link components replace the HTML <button> and <a> tags, respectively, giving you access to global variants, the sx and component props, and more.

Add the following imports with the rest in your app:

import Button from '@mui/joy/Button';
import Link from '@mui/joy/Link';

Add the following Button, Typography, and Link components after the Text Fields from the previous step, still nested inside the Sheet. Notice that the Link is appended to the Typography inside of the endDecorator prop:

<Button sx={{ mt: 1 /* margin top */ }}>
  Log in
</Button>
<Typography
  endDecorator={<Link href="/sign-up">Sign up</Link>}
  fontSize="sm"
  sx={{ alignSelf: 'center' }}
>
  Don't have an account?
</Typography>

🎁 Bonus: Build a toggle for light and dark mode

The useColorScheme hook aids in the implementation of a toggle button for switching between light and dark mode in an app. It also enables Joy UI to ensure that the user-selected mode (which is stored in localStorage by default) stays in sync across browser tabs.

Add useColorScheme to your import from @mui/joy/styles:

import { CssVarsProvider, useColorScheme } from '@mui/joy/styles';

Next, create a light/dark mode toggle button by adding the following code snippet in between your imports and your App():

function ModeToggle() {
  const { mode, setMode } = useColorScheme();
  const [mounted, setMounted] = React.useState(false);

  // necessary for server-side rendering
  // because mode is undefined on the server
  React.useEffect(() => {
    setMounted(true);
  }, []);
  if (!mounted) {
    return null;
  }

  return (
    <Button
      variant="outlined"
      onClick={() => {
        setMode(mode === 'light' ? 'dark' : 'light');
      }}
    >
      {mode === 'light' ? 'Turn dark' : 'Turn light'}
    </Button>
  );
}

Finally, add your newly built <ModeToggle /> button above <Sheet />:

 export default function App() {
   return (
     <CssVarsProvider>
+      <ModeToggle />
       <Sheet>...</Sheet>
     </CssVarsProvider>
   );
 }

Your app should now look like the interactive demo at the top of the page. Great job making it all the way to the end!

Summary

Here's a recap of the components used:

Here are some of the major features introduced:

Next steps

This tutorial does not cover theming or general component customization. Learn more about different customization approaches and when to apply them.

To see some more sophisticated examples of Joy UI in action, check out our collection of templates.

Are you migrating from Material UI? Learn how to work with Joy UI and Material UI together in one app.