Skip to main content

Overview

We welcome contributions to Stride Design System! This guide covers everything from setup to submitting pull requests, including our changeset workflow and component development patterns.

Getting Started

Prerequisites

  • Node.js 18+ and npm
  • Git
  • A GitHub account
  • Basic knowledge of React, TypeScript, and Tailwind CSS

Repository Setup

1

Clone the repository

git clone [repo-url]
cd stride-ds
npm install
2

Start development environment

# Launch Storybook for component development
npm run storybook

# Or run the Next.js development app
npm run dev
3

Create a feature branch

git checkout -b feature/my-new-component
Branch naming conventions:
  • feature/component-name - New components or features
  • fix/issue-description - Bug fixes
  • docs/what-changed - Documentation updates
  • refactor/area-changed - Code refactoring

Development Workflow

Available Scripts

# Development
npm run dev              # Next.js dev server (http://localhost:3000)
npm run storybook        # Storybook (http://localhost:6006)

# Building
npm run build           # Build Next.js app
npm run build:lib       # Build library (Vite)
npm run build:types     # Generate TypeScript types
npm run build:styles    # Build CSS styles
npm run prepublishOnly  # Complete build for publishing

# Quality
npm run lint            # Run ESLint
npm run build-storybook # Build Storybook for production

Creating a New Component

File Structure

Every component follows this structure:
src/components/ui/MyComponent/
├── index.ts                 # Exports
├── MyComponent.tsx          # Component implementation
└── MyComponent.stories.tsx  # Storybook documentation

Component Template

Use this template for new components:
src/components/ui/MyComponent/MyComponent.tsx
"use client";

import React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";

// Define variants with semantic tokens
const myComponentVariants = cva(
  [
    "base-classes",
    "[transition-duration:var(--transition-normal)]",
  ],
  {
    variants: {
      variant: {
        default: "[color:var(--text-primary)]",
        secondary: "[color:var(--text-secondary)]",
      },
      size: {
        sm: "[font-size:var(--font-size-sm)]",
        md: "[font-size:var(--font-size-md)]",
        lg: "[font-size:var(--font-size-lg)]",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "md",
    },
  }
);

// Define props interface
export interface MyComponentProps
  extends React.HTMLAttributes<HTMLDivElement>,
    VariantProps<typeof myComponentVariants> {
  children?: React.ReactNode;
}

// Implement with forwardRef
export const MyComponent = React.forwardRef<HTMLDivElement, MyComponentProps>(
  ({ className, variant, size, children, ...props }, ref) => {
    return (
      <div
        className={cn(myComponentVariants({ variant, size, className }))}
        ref={ref}
        {...props}
      >
        {children}
      </div>
    );
  }
);

MyComponent.displayName = "MyComponent";

Export Component

src/components/ui/MyComponent/index.ts
export * from './MyComponent';
src/index.ts
// Add to main index file
export * from './components/ui/MyComponent';

Storybook Development

Creating Stories

Stories document component usage and variants:
src/components/ui/MyComponent/MyComponent.stories.tsx
import type { Meta, StoryObj } from "@storybook/nextjs";
import { MyComponent } from "./MyComponent";

const meta: Meta<typeof MyComponent> = {
  title: "Stride DS/MyComponent",
  component: MyComponent,
  parameters: {
    layout: "centered",
  },
  tags: ["autodocs"],
  argTypes: {
    variant: {
      control: { type: "select" },
      options: ["default", "secondary"],
    },
    size: {
      control: { type: "select" },
      options: ["sm", "md", "lg"],
    },
  },
};

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
  args: {
    children: "My Component",
    variant: "default",
    size: "md",
  },
};

export const AllVariants: Story = {
  render: () => (
    <div className="flex gap-4">
      <MyComponent variant="default">Default</MyComponent>
      <MyComponent variant="secondary">Secondary</MyComponent>
    </div>
  ),
};

Storybook Best Practices

Document All Variants

Create a story for each variant combination to showcase all possibilities.

Interactive Controls

Use argTypes to provide interactive controls in Storybook.

Accessibility Tests

Include accessibility parameters for automated a11y testing.

Real Use Cases

Show practical examples of how the component would be used.

Design Tokens

Adding Brand Tokens

Brand-specific colors and typography go in src/app/brands.css:
src/app/brands.css
.brand-stride {
  /* Brand colors */
  --brand-accent-500: #2563eb;
  --brand-accent-600: #1d4ed8;
  
  /* Custom typography */
  --font-family-display: 'Outfit', sans-serif;
  
  /* Semantic mapping */
  --text-accent: var(--brand-accent-500);
  --interactive-primary: var(--brand-accent-500);
}

.brand-stride.dark {
  --brand-accent-500: #60a5fa;
  --brand-accent-600: #3b82f6;
}

Adding Foundation Tokens

Spacing, radius, and shadows go in src/styles.css:
src/styles.css
:root {
  /* Spacing */
  --spacing-xs: 0.25rem;
  --spacing-sm: 0.5rem;
  --spacing-md: 1rem;
  --spacing-lg: 1.5rem;
  --spacing-xl: 2rem;
  
  /* Border radius */
  --radius-sm: 0.25rem;
  --radius-md: 0.5rem;
  --radius-lg: 0.75rem;
  --radius-full: 9999px;
  
  /* Shadows */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
  --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
  --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
}

Adding Component Tokens

Component-specific tokens also go in src/styles.css:
src/styles.css
:root {
  /* Button tokens */
  --button-height-sm: 2rem;
  --button-height-md: 2.5rem;
  --button-height-lg: 3rem;
  --button-padding-sm: 0.5rem 1rem;
  --button-padding-md: 0.75rem 1.5rem;
  --button-padding-lg: 1rem 2rem;
  --radius-button: 0.5rem;
  
  /* Input tokens */
  --input-height-sm: 2rem;
  --input-height-md: 2.5rem;
  --input-height-lg: 3rem;
}

Changeset Workflow

What are Changesets?

Changesets track changes and automate versioning and changelog generation. Every PR that changes functionality must include a changeset.

Creating a Changeset

1

Make your changes

Implement your feature or fix.
2

Run changeset command

npm run changeset
3

Select change type

Choose the appropriate version bump:
  • patch (0.0.1): Bug fixes, small improvements
  • minor (0.1.0): New features, new components
  • major (1.0.0): Breaking changes
4

Write description

Provide a clear description of your changes:
✅ Good: "Add new Toast component with success/error variants"
✅ Good: "Fix Button focus styles in dark mode"
❌ Bad: "Update stuff"
❌ Bad: "Changes"
5

Commit the changeset

The changeset file is created in .changeset/ directory:
git add .changeset/
git commit -m "feat: add toast component"

Changeset Examples

npm run changeset
# Select: minor
# Description: Add Alert component with info, success, warning, and error variants

Git Commit Guidelines

Commit Message Format

Follow Conventional Commits:
type(scope): description

# Examples:
feat(button): add loading state with spinner
fix(card): resolve border radius in safari
docs(readme): update installation instructions
style(input): improve focus ring visibility
refactor(utils): simplify classname helper
test(button): add accessibility tests
chore(deps): update tailwind to latest

Commit Types

New features or components
feat(toast): add Toast component
feat(button): add leftIcon and rightIcon props
Bug fixes
fix(button): resolve focus ring in dark mode
fix(input): correct label alignment
Documentation changes
docs(readme): add Next.js 15 setup guide
docs(button): update component examples
Formatting, styling (no code logic changes)
style(card): improve spacing consistency
style: format code with prettier
Code refactoring without changing behavior
refactor(utils): simplify cn() implementation
refactor(button): extract icon logic to separate hook
Adding or updating tests
test(button): add keyboard navigation tests
test: increase coverage for Input component
Maintenance tasks, dependency updates
chore(deps): update react-aria to v3.30.0
chore: configure github actions

Pull Request Process

1

Test your changes

# Build the library
npm run build:lib
npm run build:types
npm run build:styles

# Run linting
npm run lint

# Test in Storybook
npm run storybook
2

Create changeset

npm run changeset
3

Commit and push

git add .
git commit -m "feat: add new component"
git push origin feature/my-component
4

Open pull request

  1. Go to GitHub repository
  2. Click “Compare & pull request”
  3. Fill out the PR template:
    • Clear title and description
    • Link related issues
    • Add screenshots for UI changes
    • Note breaking changes
5

Address review feedback

# Make requested changes
git add .
git commit -m "fix: address review feedback"
git push origin feature/my-component

Release Process

Automated Releases

Stride uses an automated release workflow:
  1. Developer creates changeset (you did this!)
  2. PR is merged to main branch
  3. GitHub Actions detects changesets
  4. Version Packages PR is created automatically
  5. Maintainer reviews and merges version PR
  6. Automatic publication to npm with release notes
You don’t need to manually version or publish! The changeset workflow handles everything automatically.

Component Development Patterns

React Aria Integration

Many Stride components use React Aria for accessibility:
import {
  Button as AriaButton,
  type ButtonProps as AriaButtonProps,
} from "react-aria-components";

export interface ButtonProps extends AriaButtonProps {
  // Add custom props
}

export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  (props, ref) => {
    return <AriaButton ref={ref} {...props} />;
  }
);

Always Use Semantic Tokens

// ❌ Don't use hardcoded values
const styles = "bg-blue-500 text-white";

// ✅ Use semantic tokens
const styles = "[background-color:var(--interactive-primary)] [color:var(--interactive-primary-text)]";

Proper TypeScript Types

// ✅ Export props interface
export interface ComponentProps extends HTMLAttributes<HTMLDivElement> {}

// ✅ Use VariantProps for variants
export interface ComponentProps extends VariantProps<typeof variants> {}

// ✅ Proper forwardRef typing
export const Component = React.forwardRef<HTMLDivElement, ComponentProps>();

Getting Help

Discord Community

Join our Discord for questions and discussions.

GitHub Issues

Report bugs or request features on GitHub.

Documentation

Check the docs for guides and examples.

Code Examples

Browse existing components for patterns.

Next Steps

Styling Guide

Learn styling patterns and best practices

TypeScript Guide

Master TypeScript patterns in Stride

Build docs developers (and LLMs) love