Skip to main content

Theming

Maybern extends Chakra UI with a custom theme for consistent styling.

Theme Structure

src/theme/
├── index.ts              # Theme export
├── colors.ts             # Color palette
├── components/           # Component style overrides
├── foundations/          # Base styles
└── tokens/               # Design tokens

Color Palette

// theme/colors.ts
export const colors = {
  brand: {
    50: "#e8f5e9",
    100: "#c8e6c9",
    200: "#a5d6a7",
    500: "#22c55e",  // Primary
    600: "#16a34a",
    700: "#15803d",
    800: "#166534",
    900: "#14532d",
  },
  gray: {
    50: "#fafafa",
    100: "#f4f4f5",
    // ...
  },
};

Using Theme Values

In Components

import { Box, Text, useColorModeValue } from "@chakra-ui/react";

function MyComponent() {
  const bg = useColorModeValue("white", "gray.800");
  const borderColor = useColorModeValue("gray.200", "gray.700");
  
  return (
    <Box
      bg={bg}
      borderColor={borderColor}
      borderWidth="1px"
      borderRadius="md"
      p={4}
    >
      <Text color="brand.600" fontWeight="semibold">
        Hello
      </Text>
    </Box>
  );
}

Responsive Values

<Box
  fontSize={{ base: "sm", md: "md", lg: "lg" }}
  p={{ base: 2, md: 4 }}
  display={{ base: "block", md: "flex" }}
/>

Spacing Scale

// Uses Chakra's default spacing scale
// 1 = 4px, 2 = 8px, 4 = 16px, etc.
<Box p={4} mt={2} gap={3}>
  <Text mb={2}>Content</Text>
</Box>

Component Overrides

Button

// theme/components/button.ts
export const Button = {
  baseStyle: {
    fontWeight: "semibold",
    borderRadius: "md",
  },
  variants: {
    solid: {
      bg: "brand.500",
      color: "white",
      _hover: {
        bg: "brand.600",
      },
    },
    outline: {
      borderColor: "brand.500",
      color: "brand.500",
    },
  },
  defaultProps: {
    variant: "solid",
    size: "md",
  },
};

Input

// theme/components/input.ts
export const Input = {
  variants: {
    outline: {
      field: {
        borderColor: "gray.300",
        _hover: {
          borderColor: "gray.400",
        },
        _focus: {
          borderColor: "brand.500",
          boxShadow: "0 0 0 1px var(--chakra-colors-brand-500)",
        },
      },
    },
  },
  defaultProps: {
    variant: "outline",
    size: "md",
  },
};

Dark Mode

import { useColorMode, useColorModeValue } from "@chakra-ui/react";

function ThemeToggle() {
  const { colorMode, toggleColorMode } = useColorMode();
  
  return (
    <Button onClick={toggleColorMode}>
      {colorMode === "light" ? "Dark" : "Light"} Mode
    </Button>
  );
}

// Conditional values
const bg = useColorModeValue("white", "gray.900");
const text = useColorModeValue("gray.800", "gray.100");

Typography

// theme/foundations/typography.ts
export const fonts = {
  heading: "Inter, system-ui, sans-serif",
  body: "Inter, system-ui, sans-serif",
  mono: "JetBrains Mono, monospace",
};

export const fontSizes = {
  xs: "0.75rem",
  sm: "0.875rem",
  md: "1rem",
  lg: "1.125rem",
  xl: "1.25rem",
  "2xl": "1.5rem",
};

Best Practices

Always use theme tokens instead of hardcoded values:
// Good
<Box p={4} color="gray.600" />

// Avoid
<Box padding="16px" color="#718096" />
Always handle both light and dark modes:
const bg = useColorModeValue("white", "gray.800");
Use responsive arrays/objects for breakpoints:
<Box fontSize={{ base: "sm", md: "md" }} />
Extend component styles instead of replacing them entirely.