DEV Community

Aodhan Hamilton
Aodhan Hamilton

Posted on

Responsive layout in Next.js with Chakra-ui

Earlier this week I posted an article on responsive and custom components with Chakra-ui in next Next.js.

I wanted to write a follow up and focus on a responsive layout, and the Chakra-ui Grid Component makes it a breeze and pleasure to do so.

I've made a small demo, displaying some friends on a small profile-like card, building on top of what I explained in my previous article

dev-to-demo


//pages/index.js

const Home = () => {
  const mainRef = useRef();
  const dimensions = useDimensions(mainRef, true);

  const [size, setSize] = useState('');

  useEffect(() => {
    if (dimensions) {
      let width = dimensions.borderBox.width;
      switch (true) {
        case width < 499:
          setSize('base');
          break;
        case width >= 499 && width < 696:
          setSize('sm');
          break;
        case width >= 696 && width < 945:
          setSize('md');
          break;
        case width >= 945:
          setSize('lg');
          break;
      }
    }
  }, [dimensions]);

  return (
    <>
      <Nav />
      <Box
        w="100vw"
        h="100vh"
        display="flex"
        justifyContent="center"
        ref={mainRef}
        alignItems="flex-start"
        as="main"
      >
        <Layout>
          <MyGrid />
          <Box fontSize={{ base: '20px', sm: '24px' }} mt="30px">
            <Text display="flex" justifyContent="center">
              Width: {dimensions && dimensions.borderBox.width}px
            </Text>
            <Text display="flex" justifyContent="center">
              Custom breakpoint size: {size}
            </Text>
          </Box>
        </Layout>
      </Box>
    </>
  );
};
export default Home;
Enter fullscreen mode Exit fullscreen mode

The index component returns a React Fragment containing a simple custom Nav component and a Layout container component to house our Grid component.


//components/MyGrid.js

import profiles from '../profiles.json';

const MyGrid = () => {
  return (
    <>
      <Grid
        templateColumns={{ base: '1fr', lg: 'repeat(2, 1fr)' }}
        h="100%"
        gap="10px"
        overflow="auto"
        css={{
          '&::-webkit-scrollbar': {
            width: '4px',
          },
          '&::-webkit-scrollbar-track': {
            width: '6px',
          },
          '&::-webkit-scrollbar-thumb': {
            background: 'primary',
            borderRadius: '24px',
          },
        }}
      >
        {profiles.map((profile) => (
          <GridItem
            key={profile.id}
            h="150px"
            bg="primary"
            borderRadius="10"
            color="white"
            display="flex"
            pl={{ base: '10px', sm: '25px', lg: '15px' }}
            alignItems="center"
          >
            <Box display="flex" alignItems="center">
              <ItemAvatar imageSrc={profile.imageSrc} name={profile.name} />
            </Box>
            <Details
              name={profile.name}
              title={profile.title}
              link={profile.linkSrc}
            />
          </GridItem>
        ))}
      </Grid>
    </>
  );
};

export default MyGrid;

Enter fullscreen mode Exit fullscreen mode

The Grid component imports a json file from our root and we map over that data and for each object in that array, we return a Grid Item from Chakra, with an Avatar component from Chakra and our own custom Details component inside.


//components/Details.js

import { Box, Text } from '@chakra-ui/react';
import Link from 'next/link';

const Details = ({ name, title, link }) => {
  return (
    <Box
      display="flex"
      flexDirection="column"
      pl={{ base: '10px', sm: '25px', lg: '15px' }}
      fontSize={{ base: '12px', sm: '14px', md: '16px', lg: '14px' }}
    >
      <Text fontSize="20px"> {name} </Text>
      <Text as="i">{title}</Text>
      <Text color="#03fcfc" as="i">
        <Link href={link}>{link}</Link>
      </Text>
    </Box>
  );
};

export default Details;

Enter fullscreen mode Exit fullscreen mode

Throughout this article, you notice the {{base: 'someValue', sm: 'someValue', ...}} syntax on some of the components props and you can see me explain it in more depth in my other article, but to summarize, it tells the prop to be a certain value, based on the custom breakpoints we defined in the theme.


//styles/ChakraTheme.js

import { extendTheme } from '@chakra-ui/react';

const theme = extendTheme({
  colors: {
    primary: '#201D29',
  },
  breakpoints: {
    sm: '499px',
    md: '696px',
    lg: '945px',
  },
});

export default theme;
Enter fullscreen mode Exit fullscreen mode

Finally, to help us visualize our breakpoints, we can utilize a hook from Chakra to get the the width of our Box that wraps our Layout component and store it in state, and it will update in a useEffect every time on the component resize.


import { useRef, useState, useEffect } from 'react';
import { Box, Text } from '@chakra-ui/react';
import MyGrid from '../components/MyGrid';
import Layout from '../components/Layout';
import Nav from '../components/Nav';
import { useDimensions } from '@chakra-ui/react';

const Home = () => {
  const mainRef = useRef();
  const dimensions = useDimensions(mainRef, true);

  const [size, setSize] = useState('');

  useEffect(() => {
    if (dimensions) {
      let width = dimensions.borderBox.width;
      switch (true) {
        case width < 499:
          setSize('base');
          break;
        case width >= 499 && width < 696:
          setSize('sm');
          break;
        case width >= 696 && width < 945:
          setSize('md');
          break;
        case width >= 945:
          setSize('lg');
          break;
      }
    }
  }, [dimensions]);

  return (
    <>
      <Nav />
      <Box
        w="100vw"
        h="100vh"
        display="flex"
        justifyContent="center"
        ref={mainRef}
        alignItems="flex-start"
        as="main"
      >
        <Layout>
          <MyGrid />
          <Box fontSize={{ base: '20px', sm: '24px' }} mt="30px">
            <Text display="flex" justifyContent="center">
              Width: {dimensions && dimensions.borderBox.width}px
            </Text>
            <Text display="flex" justifyContent="center">
              Custom breakpoint size: {size}
            </Text>
          </Box>
        </Layout>
      </Box>
    </>
  );
};
export default Home;

Enter fullscreen mode Exit fullscreen mode

On the initial load, the size state is a empty string const [size, setSize] = useState('') and will be updates in the useEffect, after the first render to the screen.

Every time the dimensions object from Chakra updates, it triggers the function of the useEffect. That function uses a switch statement to determine what to update the size state to.

You can see the source code here
And follow me here

Top comments (0)