Skip to main content

Overview

Stride Design System is built with TypeScript and provides comprehensive type definitions for all components. Get full IntelliSense support, type checking, and autocompletion in your IDE.

Type Exports

Component Props Types

Every Stride component exports its props interface:
import type { 
  ButtonProps,
  CardProps,
  InputProps,
  BadgeProps 
} from 'stride-ds';

Using Exported Types

import type { ButtonProps } from 'stride-ds';
import { Button } from 'stride-ds';

interface CustomButtonProps extends ButtonProps {
  loading?: boolean;
  analyticsId?: string;
}

export function CustomButton({ 
  loading, 
  analyticsId, 
  children,
  ...props 
}: CustomButtonProps) {
  return (
    <Button 
      {...props}
      isDisabled={loading || props.isDisabled}
    >
      {loading ? 'Loading...' : children}
    </Button>
  );
}

VariantProps Pattern

Extracting Variant Types

Stride uses class-variance-authority for type-safe variants:
src/components/ui/Button/Button.tsx
import { cva, type VariantProps } from 'class-variance-authority';

const buttonVariants = cva(
  'base-classes',
  {
    variants: {
      variant: {
        primary: 'primary-classes',
        secondary: 'secondary-classes',
        ghost: 'ghost-classes',
        destructive: 'destructive-classes',
      },
      size: {
        sm: 'small-classes',
        md: 'medium-classes',
        lg: 'large-classes',
      },
    },
    defaultVariants: {
      variant: 'primary',
      size: 'md',
    },
  }
);

// Extract variant types automatically
export interface ButtonProps
  extends AriaButtonProps,
    VariantProps<typeof buttonVariants> {
  children?: React.ReactNode;
  leftIcon?: React.ReactNode;
  rightIcon?: React.ReactNode;
  className?: string;
}

// ButtonProps now includes:
// - variant?: 'primary' | 'secondary' | 'ghost' | 'destructive'
// - size?: 'sm' | 'md' | 'lg'
// - All AriaButtonProps (isDisabled, onPress, etc.)

Type Inference Benefits

<Button 
  variant="  // IDE suggests: 'primary' | 'secondary' | 'ghost' | 'destructive'
  size="     // IDE suggests: 'sm' | 'md' | 'lg'

ForwardRef Pattern

Type-Safe Refs

All Stride components properly type refs using React.forwardRef:
src/components/ui/Button/Button.tsx
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, children, ...props }, ref) => {
    return (
      <AriaButton
        className={cn(buttonVariants({ variant, size, className }))}
        ref={ref}
        {...props}
      >
        {children}
      </AriaButton>
    );
  }
);

Button.displayName = "Button";

Using Refs

import { useRef } from 'react';
import { Button } from 'stride-ds';

function MyComponent() {
  const buttonRef = useRef<HTMLButtonElement>(null);
  
  const focusButton = () => {
    buttonRef.current?.focus();
  };
  
  return (
    <Button ref={buttonRef}>
      Click me
    </Button>
  );
}

Component Interface Patterns

Standard Component Props

Here’s the complete pattern used across all Stride components:
import React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import type { HTMLAttributes } from 'react';

// Define variants
const componentVariants = cva('base', {
  variants: {
    variant: { default: '', custom: '' },
    size: { sm: '', md: '', lg: '' },
  },
  defaultVariants: {
    variant: 'default',
    size: 'md',
  },
});

// Extend both HTML attributes and variant props
export interface ComponentProps
  extends HTMLAttributes<HTMLDivElement>,
    VariantProps<typeof componentVariants> {
  // Add custom props
  customProp?: string;
}

// Forward ref with proper types
export const Component = React.forwardRef<HTMLDivElement, ComponentProps>(
  ({ className, variant, size, customProp, ...props }, ref) => {
    return <div ref={ref} {...props} />;
  }
);

Component.displayName = 'Component';

React Aria Components

For components using React Aria, extend from Aria props:
src/components/ui/Input/Input.tsx
import type { TextFieldProps as AriaTextFieldProps } from 'react-aria-components';

export interface InputProps
  extends Omit<AriaTextFieldProps, 'children'>,
    VariantProps<typeof inputVariants> {
  label?: string;
  placeholder?: string;
  helperText?: string;
  errorMessage?: string;
  className?: string;
  leftIcon?: React.ReactNode;
  rightIcon?: React.ReactNode;
}

export const Input = React.forwardRef<HTMLInputElement, InputProps>(
  (props, ref) => {
    // Component implementation
  }
);

Advanced Type Patterns

Polymorphic Components

Create components that can render as different elements:
import React from 'react';

type AsProps<T extends React.ElementType> = {
  as?: T;
} & React.ComponentPropsWithoutRef<T>;

export type CardTitleProps<T extends React.ElementType = 'h3'> = AsProps<T> & {
  className?: string;
};

export const CardTitle = React.forwardRef(
  <T extends React.ElementType = 'h3'>(
    { as, className, ...props }: CardTitleProps<T>,
    ref: React.Ref<any>
  ) => {
    const Component = as || 'h3';
    return <Component ref={ref} className={className} {...props} />;
  }
);

Discriminated Unions

Use discriminated unions for mutually exclusive props:
type BaseProps = {
  label: string;
  className?: string;
};

type SingleSelectProps = BaseProps & {
  multiple?: false;
  value?: string;
  onChange?: (value: string) => void;
};

type MultiSelectProps = BaseProps & {
  multiple: true;
  value?: string[];
  onChange?: (value: string[]) => void;
};

type SelectProps = SingleSelectProps | MultiSelectProps;

// TypeScript infers correct types based on 'multiple' prop
function Select(props: SelectProps) {
  if (props.multiple) {
    // props.value is string[]
    // props.onChange is (value: string[]) => void
  } else {
    // props.value is string
    // props.onChange is (value: string) => void
  }
}

Type Utilities

ClassValue Type

The cn() utility accepts various input types:
src/lib/utils.ts
import { type ClassValue, clsx } from 'clsx';

// ClassValue accepts:
// - string: 'class-name'
// - string[]: ['class-1', 'class-2']
// - Record<string, boolean>: { 'active': true, 'disabled': false }
// - undefined/null/false (ignored)

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

Component Prop Type Extraction

Extract prop types from existing components:
import type { ComponentProps } from 'react';
import { Button } from 'stride-ds';

// Extract all Button props
type ExtractedButtonProps = ComponentProps<typeof Button>;

// Pick specific props
type ButtonVariantProp = Pick<ExtractedButtonProps, 'variant'>;

// Omit specific props
type ButtonWithoutSize = Omit<ExtractedButtonProps, 'size'>;

IntelliSense Benefits

Get suggestions for all component props, variants, and values as you type.
<Button 
  // IDE shows: variant, size, className, children, leftIcon, rightIcon
  // Plus all AriaButtonProps: onPress, isDisabled, etc.
Hover over any prop to see its documentation and type information.
<Input
  // Hover shows: label?: string | undefined
  // Description: Optional label text displayed above the input
  label="Email"
Catch type errors before runtime with instant feedback.
<Button variant="invalid"> // ❌ Error immediately shown
<Input size="xl">          // ❌ Error immediately shown
Rename components or props with confidence - TypeScript finds all usage.
// Rename 'variant' → 'appearance'
// TypeScript shows all locations that need updating

Best Practices

Always Export Types

Export component prop types for consumers to extend and reuse.

Use VariantProps

Let cva generate variant types automatically for consistency.

Proper Ref Types

Specify correct element type in forwardRef for accurate ref typing.

Extend Native Types

Extend from HTMLAttributes or React Aria types for standard HTML props.

Example: Building a Type-Safe Component

Here’s a complete example combining all patterns:
import React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';

// 1. Define variants
const alertVariants = cva(
  ['rounded-lg border p-4'],
  {
    variants: {
      variant: {
        info: 'bg-blue-50 border-blue-200 text-blue-900',
        success: 'bg-green-50 border-green-200 text-green-900',
        warning: 'bg-yellow-50 border-yellow-200 text-yellow-900',
        error: 'bg-red-50 border-red-200 text-red-900',
      },
    },
    defaultVariants: {
      variant: 'info',
    },
  }
);

// 2. Define props interface
export interface AlertProps
  extends React.HTMLAttributes<HTMLDivElement>,
    VariantProps<typeof alertVariants> {
  title?: string;
  icon?: React.ReactNode;
  onClose?: () => void;
}

// 3. Implement with forwardRef
export const Alert = React.forwardRef<HTMLDivElement, AlertProps>(
  ({ className, variant, title, icon, children, onClose, ...props }, ref) => {
    return (
      <div
        ref={ref}
        className={cn(alertVariants({ variant }), className)}
        role="alert"
        {...props}
      >
        {icon && <span className="mr-2">{icon}</span>}
        {title && <h4 className="font-semibold mb-1">{title}</h4>}
        <div>{children}</div>
        {onClose && (
          <button onClick={onClose} className="ml-auto">
            Close
          </button>
        )}
      </div>
    );
  }
);

Alert.displayName = 'Alert';

Next Steps

Styling Guide

Learn about Tailwind CSS v4 and styling patterns

SSR Guide

Configure components for server-side rendering

Build docs developers (and LLMs) love