@47ng/chakra-next

Design System for React, based on Chakra UI + Next.js, written in TypeScript.

Usage no npm install needed!

<script type="module">
  import 47ngChakraNext from 'https://cdn.skypack.dev/@47ng/chakra-next';
</script>

README

@47ng/chakra-next

NPM MIT License Continuous Integration Coverage Status Dependabot Status

Opinionated design system for React, based on Chakra UI + Next.js.

Features

  • Low-boilerplate _app.tsx
  • Batteries included but replaceable (theme, colors, fonts)
  • 100% TypeScript
  • Components:

Installation

In your Next.js app:

$ npm install @47ng/chakra-next @chakra-ui/core @emotion/core @emotion/styled emotion-theming next-transpile-modules

Usage

This package is intentionnaly not transpiled, to let Next.js do its thing according to its own Webpack settings. Because Next does not normally transpile dependencies, you will have to tell it to, using next-transpile-modules:

Create a next.config.js file at the root of your application:

// next.config.js
const withTranspilation = require('next-transpile-modules')([
  '@47ng/chakra-next',
])

module.exports = withTranspilation()

Creating _app.tsx

To simplify boilerplate, we've wrapped the necessary steps to integrate Chakra UI in the _app page:

import { createChakraNextApp } from '@47ng/chakra-next'

export default createChakraNextApp()

This will give you:

  • A default theme with:
    • System font stacks
    • TailwindCSS color palettes
  • CSS Reset from Chakra UI

Custom theme

import { createChakraNextApp, defaultTheme } from '@47ng/chakra-next'

export default createChakraNextApp({
  theme: {
    ...defaultTheme,
    colors: {
      ...defaultTheme.colors,
      // your colors here
    },
  },
})

Custom global CSS

import { createChakraNextApp } from '@47ng/chakra-next'
import { css } from '@emotion/core'

export default createChakraNextApp({
  globalCss: css`
    html,
    body {
      color: #333;
    }
  `,
})

Custom root-level providers

If you want to inject other providers or wrapper elements at the root level, for example with Redux and Apollo GraphQL:

import { createChakraNextApp } from '@47ng/chakra-next'
import { Provider as ReduxProvider } from 'react-redux'
import { ApolloProvider } from '@apollo/react-hooks'

export default createChakraNextApp({
  Providers: ({ children }) => (
    <ApolloProvider client={client}>
      <ReduxProvider store={store}>{children}</ReduxProvider>
    </ApolloProvider>
  ),
})

Components

Links

import { RouteLink, OutgoingLink, ButtonRouteLink } from '@47ng/chakra-next'

export default () => (
  <>
    {/* Integrate Next.js routes with Chakra styles */}
    <RouteLink to="/login">Login</RouteLink>

    {/* Use `as` for dynamic routes */}
    <RouteLink to="/posts/[slug]" as="/posts/foo">Login</RouteLink>

    {/* Make external links stand out */}
    <OutgoingLink href="https://github.com" showExternalIcon>
      GitHub
    </RouteLink>

    {/* For when a button looks better, still outputs an <a> tag */}
    <ButtonRouteLink to="/logout">Logout</ButtonRouteLink>
  </>
)

NavLinks

Use NavLink when you want a link to have special styling depending on the current page.

By default, NavLinks:

  • Underline their text when active
  • Are active when the current path starts with the link path

Example:

import { NavLink } from '@47ng/chakra-next'

export default () => (
  <>
    <NavLink to="/blog">Blog</NavLink>
  </>
)

The link will be active for the following paths:

Path Active
/home false
/blog true
/blog/ true
/blog/foo true
Custom active styles
import { NavLink } from '@47ng/chakra-next'

export default () => (
  <>
    <NavLink
      to="/blog"
      borderBottomWidth="3px"
      borderBottomColor="transparent"
      active={{ color: 'blue.500', borderBottomColor: 'blue.500' }}
    >
      Blog
    </NavLink>
  </>
)
Exact paths

Sometimes, you want the NavLink to be active only on exact route matches:

import { NavLink, navLinkMatch } from '@47ng/chakra-next'

export default () => (
  <>
    <NavLink to="/home" shouldBeActive={navLinkMatch.exact}>
      Home
    </NavLink>
  </>
)

You can also have custom logic to determine whether a NavLink should be active:

import { NavLink, navLinkMatch } from '@47ng/chakra-next'

export default () => (
  <>
    <NavLink
      to="/blog/[post]"
      as="/blog/another-blog-post?active=true"
      shouldBeActive={({ to, as, router }) =>
        navLinkMatch.exact({ to, as, router }) &&
        router?.query.active === 'true'
      }
    >
      Another Blog Post
    </NavLink>
  </>
)

Redirect

Redirect will change the current URL to the one given, when mounted.

import { Redirect } from '@47ng/chakra-next'

export default ({ loggedIn }) => (
  <>{loggedIn ? <Text>Hello !</Text> : <Redirect to="/login" />}</>
)

By default, the redirection will be pushed onto the navigation history stack. You can replace the history stack instead with the replace prop:

import { Redirect } from '@47ng/chakra-next'

export default () => (
  <>
    <Redirect to="/home" replace />
  </>
)

Next.js dynamic paths are also supported:

import { Redirect } from '@47ng/chakra-next'

export default () => (
  <>
    <Redirect to="/blog/[slug]" as="/blog/foo-bar" />
  </>
)

If you want to redirect to an external link (not an internal route), you will have to set the external prop:

import { Redirect } from '@47ng/chakra-next'

export default () => (
  <>
    <Redirect to="https://example.com" external />

    {/* You can also have the history replaced with external URLs: */}
    <Redirect to="https://example.com" external replace />
  </>
)

Containers

import { Container, FlexContainer, StackContainer } from '@47ng/chakra-next'

export default () => (
  <>
    {/* Container as Box */}
    <Container>I am centred and width-limited</Container>

    {/* Container + Flex */}
    <FlexContainer>
      <Box>Direction is column by default</Box>
      <Box>Foo</Box>
      <Box>Bar</Box>
      <Box>Egg</Box>
    </FlexContainer>

    {/* Container + Stack */}
    <StackContainer spacing={8}>
      <Box>Foo</Box>
      <Box>Bar</Box>
      <Box>Egg</Box>
    </StackContainer>
  </>

  {/* All containers can be wider */}
  <Container wide>I am centred and width-limited</Container>
)

Cards

import { Card, FlexCard, StackCard } from '@47ng/chakra-next'

export default () => (
  <>
    {/* Card as Box */}
    <Card>I'm in a card</Card>

    {/* Card + Flex */}
    <FlexCard>
      <Box>Direction is column by default</Box>
      <Box>Foo</Box>
      <Box>Bar</Box>
      <Box>Egg</Box>
    </FlexCard>

    {/* Card + Stack */}
    <StackCard spacing={8}>
      <Box>Foo</Box>
      <Box>Bar</Box>
      <Box>Egg</Box>
    </StackCard>
  </>
)

SvgBox

Composes PseudoBox with an SVG tag, with:

  • SVG namespace pre-filled
  • role="img"
import { SvgBox } from '@47ng/chakra-next'

export default () => (
  <SvgBox
    aria-labelledby="svgbox-demo-title svgbox-demo-desc"
    viewBox="0 0 24 24"
    display="block"
    my={4}
    mx="auto"
  >
    <title id="svgbox-demo-title">A red circle</title>
    <desc id="svgbox-demo-desc">
      SvgBox lets you style SVG container tags with Chakra UI style props.
    </desc>
    <circle fill="red" cx="12" cy="12" r="10">
  </SvgBox>
)

Note: For now, we can't use theme color shorthands like red.200 for fills & strokes, it might come in a later update.

NoSSR

Sometimes you want to render a component only on the client, and have a skeleton or fallback component rendered on the server, whether for SSR or static output.

import { NoSSR } from '@47ng/chakra-next'

export default () => (
  <>
    <NoSSR>This is only rendered on the client</NoSSR>

    {/* Skeleton is rendered on SSR/SSG, TheRealThing is rendered on the client.*/}
    <NoSSR fallback={<Skeleton />}>
      <TheRealThing />
    </NoSSR>
  </>
)

Examples

Header with navigation links:

import { Box, Stack } from '@chakra-ui/core'
import { NavLink } from '@47ng/chakra-next'

export default () => (
  <Box as="header">
    <Stack as="nav" isInline>
      <NavLink to="/features">Features</NavLink>
      <NavLink to="/pricing">Pricing</NavLink>
      <NavLink to="/docs">Documentation</NavLink>
    </Stack>
  </Box>
)

License

MIT - Made with ❤️ by François Best.