Skip to content
Vy logo
Guides

How to create a new React component

Our React implementation is based on Chakra UI, serving as a wrapper around various existing Chakra UI components. If you're looking for a prop that's not documented on our documentation pages, it might be helpful to check Chakra UI's documentation.

Our design system also leverages similar techniques to Chakra UI, such as placing most styles in a separate theme package and accessing them via useStyleConfig and useMultiStyleConfig. We'll discuss how this works in detail below.

Structure

The React implementation of our design system is organized into several smaller packages. Each package contains one or more relevant components for a specific domain. For example, datepicker might only contain a DatePicker component (and its subcomponents), while typography might include Text, Heading, and Code.

Package Management

All packages are located in the packages/ directory of the repository. They are not intended to be used individually but rather through a "proxy package" like spor-react, which imports and re-exports all the packages' exports. This setup allows for the possibility of overriding a single package if necessary without upgrading all others simultaneously.

Each package contains minimal configuration, primarily a package.json to define the package name and dependencies, and a tsconfig.json that extends a common configuration file.

Component creation steps

Step 1: Set Up the Component File

Create a new file for your component, e.g., Button.tsx. Import the necessary modules from React, Chakra UI, and other utility files.

import {
ButtonProps as ChakraButtonProps,
Box,
forwardRef,
useButtonGroup,
useStyleConfig,
} from "@chakra-ui/react";
import React from "react";

Example import

Step 2: Define the Component's Props

Create a TypeScript type for your component's props, extending from Chakra UI's ButtonProps and adding any custom properties.

export type ButtonProps = Exclude<
ChakraButtonProps, "size" | "variant"
> & {
/**
* The size of the button.
*
* Defaults to "md"
* */
size?: "xs" | "sm" | "md" | "lg";
/** The different variants of a button
*
* Defaults to "primary".
*
* "control" is deprecated, and will be removed in a future version
*/
variant?:
| "control"
| "primary"
| "secondary"
| "tertiary"
| "ghost"
| "floating";
};

Example types

Step 3: Create the Component

Define the component using React's forwardRef to allow ref forwarding and Chakra UI's useStyleConfig for applying styles.

export const Button = forwardRef<ButtonProps, "button">(({
children, variant, size /* And other relevent props */
}, ref) => {
const styles = useStyleConfig("Button", { variant, size })
return (
/* Your component code here */
<Box
as={as}
type={type}
__css={styles}
/* And other relevent attributes */
>
{children}
</Box>
)
})

Example component

Step 4: Add Component Documentation

Document your component within the file using JSDoc comments. This documentation should describe the component's purpose, usage, and available props.

/**
* Buttons are used to trigger actions.
*
* There are several button variants. You can specify which one you want with the `variant` prop. The available variants are:
*
* - `primary`: This is our main button. It's used for the main actions in a view, like a call to action. There should only be a single primary button in each view.
* - `secondary`: Used for secondary actions in a view, and when you need to make several actions available at the same time.
* - `tertiary`: Used for additional choices, like a less important secondary action.
* - `ghost`: Used inside other interactive elements, like date pickers and input fields.
* - `floating`: Used for floating actions, like a menu button in a menu.
*
* ```tsx
* <Button variant="primary" onClick={confirmOrder}>
* Buy trip
* </Button>
* ```
*
* There are also different sizes. You can specify which one you want with the `size` prop. The available sizes are "lg", "md", "sm" and "xs".
*
* ```tsx
* <Button variant="secondary" size="sm" onClick={cancelOrder}>
* Cancel trip
* </Button>
* ```
*
* @see https://spor.vy.no/components/button
*/

Example JSdoc

Styling

Most of the styling in our design system can be found in the theme package. This package contains two directories: foundations and components. The foundations directory includes all our design tokens, such as colors, borders, shadows, and spacing. The components directory contains the style implementations for various components.

Style Configurations

There are two types of style configurations:

  • For components with a single DOM element.
  • For components with multiple DOM elements.
import { defineStyleConfig } from "@chakra-ui/react";
const buttonConfig = defineStyleConfig({
baseStyle: {
borderRadius: "md",
fontWeight: "bold",
},
variants: {
primary: {
bg: "blue.500",
color: "white",
_hover: {
bg: "blue.600",
},
},
},
sizes: {
lg: {
fontSize: "lg",
px: 6,
py: 3,
},
},
defaultProps: {
size: "md",
variant: "primary",
},
});
export default buttonConfig;

Example of a Single DOM Element Component

Useful Guidelines

  • Use the size prop for defining sizes and utilize t-shirt sizes (sm, md, lg, etc.).
  • Use the variant prop for defining different design variants (outline, solid, ghost, etc.).
  • Use the colorScheme prop for defining different color variants (green, teal, gray, etc.).
  • Avoid boolean props in favor of enums (e.g., loadState: "idle" | "loading" | "success" | "error").
  • Prefix all boolean props with is, such as isDisabled, isLoading, or isSpecial.
  • Write good JSDoc comments on all public components and props to make them easier to use.
  • Break up large components into smaller, more manageable components that compose well via children.
  • Try to use as few props as possible.
  • Avoid external dependencies unless they provide significant value.

Using the Color System in Style Configurations

We have implemented a color system that supports both light and dark modes. Additionally, our design system utilizes theming. Therefore, it is crucial to use the correct values when defining colors. Using outdated aliases or hardcoding HEX values directly in the code is generally discouraged.

Our design system uses a consistent color naming scheme across all themes, as defined in vy-digital.json or other theme files. These color values should be used with the mode function in Chakra UI to support theming and dark mode. We have utility files to handle color assignments for different variants, which should be utilized in style configurations.

Defining Colors in Style Config

In the style configuration files, use the utility functions to set colors based on the theme. This ensures consistency and simplifies the application of light and dark mode settings.

Example Usage

Step 1: Import Utility Functions

Import the relevant utility functions from the utils directory.

import { baseBorder, baseBackground, accentBackground, floatingBackground, floatingBorder } from "../utils";

Example import utilities

Step 2: Define Variants with Mode

Use the utility functions for example within the variants object in your style config.

const buttonConfig = defineStyleConfig({
variants: {
base: (props) => ({
cursor: "pointer",
...baseBorder("default", props),
_hover: {
...baseBorder("hover", props),
},
_active: {
...baseBackground("active", props),
...baseBorder("active", props),
},
}),
accent: (props) => ({
...accentBackground("default", props),
boxShadow: "sm",
_hover: {
...accentBackground("hover", props),
boxShadow: "md",
},
_active: {
...accentBackground("active", props),
boxShadow: "none",
},
}),
floating: (props) => ({
...floatingBackground("default", props),
...floatingBorder("default", props),
boxShadow: "sm",
_hover: {
...floatingBackground("hover", props),
...floatingBorder("hover", props),
boxShadow: "md",
},
_active: {
...floatingBorder("default", props),
...floatingBackground("active", props),
boxShadow: "none",
},
}),
},
});

Example styleConfig

Utility Functions for Colors

The utility functions encapsulate the logic for selecting the appropriate color based on the current theme (light or dark mode). These functions should be used to maintain consistency and readability across the codebase.

For more details on the utility functions, refer to the Spor GitHub repository.

Creating a PR and publish package

When creating a new PR, you need to follow these steps for approval:

1. Bump version in the docs > package.json. This is to ensure changes will appear in the documentation.

"version": "0.0.29",

Package.json version

2. Create a changeset

  • Run npx changeset and follow the prompts to create a changeset. You select packages using the space key, and go forward using the enter key.
  • Decide if it's a major, minor or patch.
  • Write a good description of what changes has been done.

3. Merge and publish

  • When the PR is approved and merged, a new PR names "Version Packages" will appear after the build. It can contain 1 or more changes.
  • Merge PR and a new package will be published.

Accessibility in Chakra UI

Ensuring accessibility is a crucial part of creating any component. Chakra UI provides several built-in features and guidelines to help developers create accessible components by default.

ARIA Attributes

ARIA (Accessible Rich Internet Applications) attributes are used to enhance the accessibility of web applications by providing additional information to assistive technologies. Include relevant ARIA attributes to ensure all users, including those who rely on screen readers, can interact with the component effectively.

Focus Management

Proper focus management is vital for users navigating with a keyboard. Chakra UI offers utility functions to handle focus states and ensure that interactive elements are focusable and follow a logical order.

Focus Styles

Chakra UI provides default focus styles that comply with accessibility standards. However, you can customize these styles using the focusVisibleStyles utility.

Keyboard Navigation

Ensure that all interactive elements, such as buttons and links, are accessible via keyboard. Users should be able to navigate to these elements using the Tab key and activate them using the Enter or Space keys.

Use Semantic HTML

Using semantic HTML elements is fundamental for accessibility. Elements such as <button>, <a>, and <input> come with built-in accessibility features that help assistive technologies understand their purpose.

Handling Dynamic Content

When a component's content changes dynamically, such as when loading states or error messages are displayed, update ARIA attributes and notify assistive technologies of these changes.

Accessibility Testing

Testing for accessibility is an ongoing process. Tools like Axe, Lighthouse, and the built-in browser accessibility inspector can help identify and fix accessibility issues. Additionally, consider user testing with individuals who rely on assistive technologies.

For more detailed overview, see our accessibility page.

When to Use styleConfig vs multiStyleConfig

styleConfig

Use styleConfig when you need to define styles for a single component. This method is ideal for simple components where all styles can be managed within a single configuration.

import { defineStyleConfig } from "@chakra-ui/react";
const buttonConfig = defineStyleConfig({
baseStyle: {
borderRadius: "md",
fontWeight: "bold",
},
variants: {
primary: {
bg: "blue.500",
color: "white",
_hover: {
bg: "blue.600",
},
},
},
sizes: {
lg: {
fontSize: "lg",
px: 6,
py: 3,
},
},
defaultProps: {
size: "md",
variant: "primary",
},
});
export default buttonConfig;

Example of defineStyleConfig

multiStyleConfig

Use multiStyleConfig when you need to define styles for a component with multiple parts, each with its own styling needs. This method is suitable for more complex components like forms, cards, or menus, where you need to manage styles for different subcomponents.

By following these guidelines and utilizing Chakra UI's built-in features, you can create accessible, well-styled components that enhance the user experience.

import { defineMultiStyleConfig } from "@chakra-ui/react";
const cardConfig = defineMultiStyleConfig({
parts: ["container", "header", "body", "footer"],
baseStyle: {
container: {
borderRadius: "md",
boxShadow: "sm",
},
header: {
fontWeight: "bold",
borderBottom: "1px solid",
borderColor: "gray.200",
},
body: {
padding: 4,
},
footer: {
padding: 4,
borderTop: "1px solid",
borderColor: "gray.200",
},
},
variants: {
outlined: {
container: {
border: "1px solid",
borderColor: "gray.200",
},
},
},
defaultProps: {
variant: "outlined",
},
});
export default cardConfig;

Example of multiStyleConfig