Site settings
Change the appearance of the site to suit your preferences
How to Create a React Component
📁 Project Structure
Each component lives in packages/spor-react/src and should follow the same structure as in Figma for easy reference.
A typical component folder contains:
ComponentName.tsx– The main component file.ComponentName.test.tsx– The test file (optional but recommended).index.ts– Exports the component.
And for the styling, add it to the /theme/recipes or /theme/slot-recipes folder:
component.ts– Defines the component’s styles.
🚀 Step-by-Step Guide
1️⃣ Create the Component File
Create a new file in the appropriate folder and name it after the component (e.g., Button.tsx).
import { forwardRef } from "react";import {type ButtonProps as ChakraButtonProps,Button as ChakraButton,type RecipeVariantProps,} from "@chakra-ui/react";type ButtonVariantProps = RecipeVariantProps<typeof buttonRecipe>;export type ButtonProps = Omit<ChakraButtonProps,"size" | "variant" | "colorPalette" // Exclude props from Chakra if needed> & PropsWithChildren<ButtonVariantProps> & {/* The props */}export const Button = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {const { variant = "primary", size = "md", ...rest } = props;const recipe = useRecipe({ key: "button" });const styles = recipe({ variant, size });return (<ChakraButton ref={ref} css={styles.root} {...rest}>{props.children}</ChakraButton>);});
2️⃣ Define the Component Recipe
Component styles should be placed in a separate recipe.ts file. Define styles using defineRecipe.
import { defineRecipe } from "@vygruppen/spor-react";export const buttonRecipe = defineRecipe({base: {root: {fontWeight: "bold",borderRadius: "md",},},variants: {variant: {primary: {root: {backgroundColor: "brand.surface",color: "white",_hover: {backgroundColor: "brand.surface.hover"},},},floating: {root: {backgroundColor: "floating.surface",color: "white",_hover: {backgroundColor: "floating.surface.hover"},},},},size: {sm: {root: {fontSize: "sm",paddingX: 3,paddingY: 2}},md: {root: {fontSize: "md",paddingX: 4,paddingY: 3}},},},});
📌 Best practices when defining a recipe:
- Use design tokens instead of hardcoded values for colors, spacing, and typography.
- Use full names for properties – instead of shorthand like
bgor px, use backgroundor paddingXfor better clarity. - Define defaults in
defaultPropsinstead of in the recipe – sometimes, defining defaultVariantinside the recipe can cause a bug where other variants inherit styles from the default one.
3️⃣ Export the Component
Ensure your component is exported from index.ts.
export * from "./Button";
And remember to add your recipe to the the theme system:
import { buttonRecipe } from '../button'export const recipes = {button: buttonRecipe,/* Other recipes */}
4️⃣ Writing Tests
We use Vitest and Testing Library for testing. Here’s an example of a test:
import { render, screen } from "@testing-library/react";import { Button } from "./Button";describe("<Button />", () => {it("renders correctly", () => {render(<Button>Click me</Button>);expect(screen.getByText("Click me")).toBeInTheDocument();});});
🔍 Use vitest-axe to check for accessibility violations:
import { axe } from "vitest-axe";it("is accessible", async () => {const { container } = render(<Button>Click me</Button>);expect(await axe(container)).toHaveNoViolations();});
5️⃣ Create a Changeset & Open a PR
Since this is a new component, create a changeset so it gets included in the next release.
npx changeset
Follow the prompts to document the changes, commit the changeset, and push your branch.
Once the PR is merged, a Version Packages PR will be generated. Merge that to publish a new release! 🎉
🎯 Best Practices
✔ Use design tokens (brand.primary, spacing.md) instead of hardcoded values.
✔ Keep components small & modular—split complex components into smaller ones.
✔ Use props instead of deeply nested children when possible.
✔ Follow naming conventions (variant, size, colorPalette).
✔ Ensure accessibility—always test with keyboard & screen readers.
🚀 Now you're ready to build new components in Spor! 🎨