Skip to content
Vy logo
Guides

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 bg or px, use background or paddingX for better clarity.
  • Define defaults in defaultProps instead of in the recipe – sometimes, defining defaultVariant inside 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! 🎨