Contributing to CUCL
How to add a new component, write stories and tests, submit a pull request, and release a new version
Contributing to CUCL
This guide walks through every step required to contribute to the Cookest UI Components Library — from local setup to an accepted pull request.
Prerequisites
| Tool | Version | Install |
|---|---|---|
| Node.js | 20+ | nodejs.org |
| Bun | 1.x | npm install -g bun |
| Git | latest | git-scm.com |
Local setup
# Clone the repository
git clone https://github.com/Cookest/cookest-ui-components-library.git
cd cookest-ui-components-library
# Install dependencies
bun install
# Start Storybook
bun run storybookStorybook runs at http://localhost:6006. Every component has live stories you can interact with.
Adding a new component
1. Create the folder structure
mkdir src/components/ComponentName
touch src/components/ComponentName/ComponentName.tsx
touch src/components/ComponentName/ComponentName.test.tsx
touch src/components/ComponentName/ComponentName.stories.tsx
touch src/components/ComponentName/index.ts2. Implement the component
Requirements:
- Accept a
className?: stringprop and merge it last withcn() - Use only
var(--ck-*)CSS variables for colours - Export prop interface and variant/size types
- Use
forwardRefif wrapping a native DOM element - Follow the accessibility requirements in Best Practices
Minimum skeleton:
// src/components/ComponentName/ComponentName.tsx
"use client";
import { forwardRef, type HTMLAttributes, type ReactNode } from "react";
import { cn } from "../../utils/cn";
export interface ComponentNameProps
extends Omit<HTMLAttributes<HTMLDivElement>, "className"> {
className?: string;
children: ReactNode;
}
export const ComponentName = forwardRef<HTMLDivElement, ComponentNameProps>(
({ className, children, ...props }, ref) => {
return (
<div
ref={ref}
className={cn("font-sans text-[var(--ck-text)]", className)}
{...props}
>
{children}
</div>
);
},
);
ComponentName.displayName = "ComponentName";3. Create the index.ts re-export
// src/components/ComponentName/index.ts
export { ComponentName } from "./ComponentName";
export type { ComponentNameProps } from "./ComponentName";4. Add to the barrel export
Open src/index.ts and add the exports in alphabetical order:
export { ComponentName } from "./components/ComponentName";
export type { ComponentNameProps } from "./components/ComponentName";5. Write Storybook stories
Create stories for every variant and state. See the Best Practices page for the required story list.
// src/components/ComponentName/ComponentName.stories.tsx
import type { Meta, StoryObj } from "@storybook/react";
import { ComponentName } from "./ComponentName";
const meta: Meta<typeof ComponentName> = {
title: "Components/ComponentName",
component: ComponentName,
tags: ["autodocs"],
};
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: { children: "Example" },
};6. Write unit tests
Tests must cover all variants, states, and interactions:
bun run test7. Type-check
bun run lintThere must be zero TypeScript errors before opening a pull request.
Modifying an existing component
- Read the existing component's test file to understand the current contract.
- Make the change — keep it backward-compatible unless the PR is a documented breaking change.
- Update the test file to cover the new behaviour.
- Update the stories file to demonstrate the new prop or variant.
- Update the Components documentation page with the new prop in the API table.
Adding or updating design tokens
- Edit the relevant file in
src/tokens/. - If adding a new CSS variable, also add it to
:rootand.darkinsrc/styles.css. - Run
bun run buildand verify the token exports indist/tokens/.
Any change to an existing token value is a potentially breaking change. Audit all component usages before renaming or removing a token.
Pull request workflow
Branch naming
<type>/<component-or-scope>-<short-description>Examples:
feat/skeleton-card-variantfix/input-aria-describedbydocs/button-icon-examplesrefactor/select-keyboard-navigation
Commit format
feat(skeleton): add SkeletonCard pre-built variant
fix(input): correct aria-describedby link when error is absentSee Best Practices for the full commit convention.
PR description template
## What
Brief description of the change.
## Why
Link to issue or explain the motivation.
## How
Key implementation decisions.
## Testing
How you verified the change.
## Screenshots
Storybook screenshot showing the new/changed component.Review checklist
Before requesting review, confirm all items:
-
bun run lintpasses (zero TypeScript errors) -
bun run testpasses (all tests green) - New component: stories cover all variants and states
- New component: added to
src/index.tsbarrel - Modified component: existing tests updated
- Token change:
src/styles.cssupdated - Documentation page updated if API changed
- No secrets or credentials in code
Running the full CI check locally
# Type check
bun run lint
# Tests
bun run test
# Format check
bun run format:check
# Build (confirms the library distributes correctly)
bun run buildAll four commands must succeed before pushing.
Storybook accessibility checks
CUCL includes @storybook/addon-a11y. Run automated accessibility checks on every new story:
- Open Storybook at
http://localhost:6006 - Navigate to the component story
- Click the Accessibility panel at the bottom
- Resolve any violations before submitting the PR
Versioning
CUCL follows Semantic Versioning:
| Change | Version bump |
|---|---|
| New component or non-breaking prop | Patch or minor |
| Breaking prop rename or removal | Major |
| Token value change affecting visual output | Minor |
| Token rename or removal | Major |
Breaking changes require:
!in the commit type (e.g.,feat(button)!: …)BREAKING CHANGE:footer in the commit body- Documentation of the migration path in the PR description