Contributing

How to contribute

TypeScript UI is an open-source project. We welcome contributions of new components, bug fixes, documentation improvements, and feature requests. This guide walks you through setting up your environment and the process of adding components.


Prerequisites

Before you start contributing, make sure you have:

  • Node.js 20+ — Required for building and running the development server
  • pnpm — Package manager used by this project (install with npm install -g pnpm)
  • Git — For cloning the repository and version control
  • A text editor — VS Code, Cursor, or your preferred code editor

Getting started

1. Clone the repository

git clone https://github.com/boomspot/typescript-ui.git
cd typescript-ui

2. Install dependencies

pnpm install

3. Start the development server

pnpm dev

The documentation site will be available at http://localhost:3000. The dev server supports hot module reloading, so changes you make to files will appear instantly.

4. Run linting and type checks

pnpm lint
pnpm exec tsc --noEmit

These commands check your code for style issues and TypeScript errors.


Adding a new primitive

Primitives are low-level, reusable components built on Radix UI. They are copy-paste components that users can import and customize. Follow these steps to add a new primitive:

Step 1: Create the component file

Create a new file in src/components/ui/shadcn/ with your component name:

// src/components/ui/shadcn/your-component.tsx
'use client';

import { forwardRef } from 'react';
import { cn } from '@/lib/utils';

export interface YourComponentProps
  extends React.HTMLAttributes<HTMLDivElement> {
  // Define your props here
}

export const YourComponent = forwardRef<
  HTMLDivElement,
  YourComponentProps
>(({ className, ...props }, ref) => (
  <div
    ref={ref}
    className={cn('base-styles dark:dark-styles', className)}
    {...props}
  />
));

YourComponent.displayName = 'YourComponent';

export default YourComponent;

Step 2: Create a demo component

Create a demo file in src/components/demos/ to showcase the primitive:

// src/components/demos/YourComponentDemo.tsx
'use client';

import YourComponent from '@/components/ui/shadcn/your-component';

export default function YourComponentDemo() {
  return (
    <div className="space-y-4">
      <YourComponent>Example content</YourComponent>
    </div>
  );
}

Step 3: Create a documentation page

Create a Markdoc page under src/app/components/shadcn/<name>/page.md:

---
title: Your Component
nextjs:
  metadata:
    title: Your Component - TypeScript UI
    description: A brief description of what your component does.
---

A clear explanation of your component's purpose and use cases.

---

## Component Demo

{% shadcn-your-component /%}

## Usage

\`\`\`tsx
import YourComponent from '@/components/ui/shadcn/your-component'

export function Example() {
  return <YourComponent>Hello</YourComponent>
}
\`\`\`

## Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| ... | ... | ... | ... |

Replace shadcn-your-component with your actual tag name (all lowercase, dashes instead of camelCase).

Step 4: Register the demo tag

Add your demo to src/markdoc/tags.js:

import YourComponentDemo from '@/components/demos/YourComponentDemo';

const tags = {
  // ... existing tags
  'shadcn-your-component': {
    selfClosing: true,
    render: YourComponentDemo,
  },
};

Step 5: Add navigation entry

Add your component to src/lib/navigation.ts under the Primitives section:

// In navigation.ts, add to the Primitives section links array:
export const navigation = [
  {
    title: 'Primitives',
    links: [
      { title: 'Your Component', href: '/components/shadcn/your-component' },
      // ... other primitives
    ],
  },
];

Adding a new section group

Section groups are collections of related components like features, CTAs, pricing, etc. Sections are published to npm and used as-is (not copy-paste). Follow these steps:

Step 1: Create the section folder

Create a new directory under src/components/ui/<group-name>/:

src/components/ui/your-sections/
├── YourSection1.tsx
├── YourSection2.tsx
└── index.ts

Step 2: Create your section components

Build your components with the naming convention: named export + Props interface + default export:

// src/components/ui/your-sections/YourSection1.tsx
'use client';

import clsx from 'clsx';

export interface YourSection1Props {
  title: string;
  description: string;
  className?: string;
}

export function YourSection1({
  title,
  description,
  className,
}: YourSection1Props) {
  return (
    <div className={clsx('py-16', className)}>
      <h2 className="text-3xl font-bold text-slate-900 dark:text-white">
        {title}
      </h2>
      <p className="mt-4 text-slate-600 dark:text-slate-400">
        {description}
      </p>
    </div>
  );
}

export default YourSection1;

Step 3: Create an index file

Export all components from src/components/ui/your-sections/index.ts:

export { YourSection1, type YourSection1Props } from './YourSection1';
export { YourSection2, type YourSection2Props } from './YourSection2';

Step 4: Create a showcase layout

Create src/components/YourSectionsSectionLayout.tsx to display all variants:

'use client';

import { YourSection1, YourSection2 } from '@/components/ui/your-sections';

const YourSectionsSectionLayout: React.FC = () => {
  return (
    <div className="space-y-24">
      <section>
        <h3 className="mb-6 text-xl font-semibold text-slate-900 dark:text-white">
          Your Section 1
        </h3>
        <YourSection1
          title="Example Title"
          description="Example description"
        />
      </section>

      <section>
        <h3 className="mb-6 text-xl font-semibold text-slate-900 dark:text-white">
          Your Section 2
        </h3>
        <YourSection2 title="Another Title" />
      </section>
    </div>
  );
};

export default YourSectionsSectionLayout;

Step 5: Create a documentation page

Create src/app/components/your-sections/page.md:

---
title: Your Sections
nextjs:
  metadata:
    title: Your Sections - TypeScript UI
    description: Pre-built section components for [description].
---

Brief introduction to your section group.

---

## Component Variants

{% yoursectionssections /%}

## Usage

\`\`\`tsx
import { YourSection1, YourSection2 } from 'typescript-ui/your-sections';

export default function Page() {
  return <YourSection1 title="..." description="..." />;
}
\`\`\`

## Props

...

Step 6: Register the showcase tag

Add to src/markdoc/tags.js:

import YourSectionsSectionLayout from '@/components/YourSectionsSectionLayout';

const tags = {
  // ... existing tags
  'yoursectionssections': {
    selfClosing: true,
    render: YourSectionsSectionLayout,
  },
};

Step 7: Add navigation entry

Add to src/lib/navigation.ts under the Sections category:

// In navigation.ts, add to the Sections section links array:
export const navigation = [
  {
    title: 'Sections',
    links: [
      { title: 'Your Sections', href: '/components/your-sections' },
      // ... other sections
    ],
  },
];

Step 8: Export from main index

Add to src/components/ui/index.ts:

export * from './your-sections';

Step 9: Configure tsup build

Add an entry to tsup.config.ts:

export default defineConfig({
  entry: {
    // ... existing entries
    'your-sections/index': 'src/components/ui/your-sections/index.ts',
  },
  // ...
});

Step 10: Update package.json

Add an export to package.json:

{
  "exports": {
    "./your-sections": {
      "types": "./dist/your-sections/index.d.ts",
      "import": "./dist/your-sections/index.mjs",
      "require": "./dist/your-sections/index.js"
    }
  }
}

Coding conventions

Follow these conventions for consistency:

Component structure

  • Use named exports for the component function and a default export
  • Always define a Props interface with full documentation
  • Use forwardRef for components that accept refs
  • Add displayName to components using forwardRef

Styling

  • Use Tailwind CSS classes exclusively—no inline styles or CSS modules
  • Follow the slate/sky palette for default colors
  • Always include dark mode support with dark: variants
  • Use clsx or cn (from @/lib/utils) for dynamic classes

Client/Server directives

  • Use 'use client' only when necessary (hooks, event handlers, interactivity)
  • Leave Server Component directives off by default
  • Section components typically need 'use client' for interactivity

Example component

'use client';

import clsx from 'clsx';
import { forwardRef } from 'react';

export interface MyComponentProps
  extends React.HTMLAttributes<HTMLDivElement> {
  variant?: 'default' | 'secondary';
  size?: 'sm' | 'md' | 'lg';
}

export const MyComponent = forwardRef<HTMLDivElement, MyComponentProps>(
  ({ variant = 'default', size = 'md', className, ...props }, ref) => (
    <div
      ref={ref}
      className={clsx(
        'rounded-lg transition-colors',
        variant === 'default' &&
          'bg-sky-500 text-white hover:bg-sky-600 dark:bg-sky-600 dark:hover:bg-sky-700',
        variant === 'secondary' &&
          'bg-slate-200 text-slate-900 hover:bg-slate-300 dark:bg-slate-700 dark:text-white dark:hover:bg-slate-600',
        size === 'sm' && 'px-2 py-1 text-sm',
        size === 'md' && 'px-4 py-2 text-base',
        size === 'lg' && 'px-6 py-3 text-lg',
        className,
      )}
      {...props}
    />
  ),
);

MyComponent.displayName = 'MyComponent';

export default MyComponent;

Pre-PR checks

Before submitting a pull request, run these checks locally:

1. ESLint

pnpm lint

Ensures your code follows style guidelines. Fix issues automatically where possible:

pnpm lint --fix

2. TypeScript

pnpm exec tsc --noEmit

Checks for type errors without emitting files.

3. Build the docs site

pnpm build

Ensures the documentation site builds without errors.

4. Build the library

pnpm build:lib

Tests that the npm package builds correctly. Check the dist/ directory for output.


Pull request guidelines

When opening a pull request:

  1. Clear title — Describe what you're adding or fixing
  2. Detailed description — Explain the why and how
  3. Minimal scope — One feature or fix per PR
  4. Documentation — Include updated docs for new components
  5. Testing — Ensure pre-PR checks pass
  6. Examples — Link to relevant examples or screenshots

Questions?

If you have questions about contributing, open an issue or discussion on GitHub. We're here to help.

Previous
Accessibility