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 */<Boxas={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 asisDisabled
,isLoading
, orisSpecial
. - 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 thespace key
, and go forward using theenter 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