Skip to main content
The Controls addon allows you to dynamically edit component props (args) directly from the Storybook UI. It automatically generates input controls based on your component’s arg types.

Installation

npm install @storybook/addon-ondevice-controls

Setup

Register the addon in your .storybook/main.ts:
import type { StorybookConfig } from '@storybook/react-native';

const main: StorybookConfig = {
  addons: [
    '@storybook/addon-ondevice-controls',
  ],
};

export default main;

Usage

Define args in your story:
import type { Meta, StoryObj } from '@storybook/react-native';
import { Button } from './Button';

const meta = {
  component: Button,
  argTypes: {
    variant: {
      control: { type: 'select' },
      options: ['primary', 'secondary', 'outline'],
    },
    size: {
      control: { type: 'radio' },
      options: ['small', 'medium', 'large'],
    },
    disabled: {
      control: 'boolean',
    },
  },
} satisfies Meta<typeof Button>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Interactive: Story = {
  args: {
    text: 'Press me',
    variant: 'primary',
    size: 'medium',
    disabled: false,
  },
};

Control Types

The Controls addon supports these control types:

Text

For string values:
argTypes: {
  label: {
    control: 'text',
  },
}

Number

For numeric values with optional constraints:
argTypes: {
  count: {
    control: { type: 'number', min: 0, max: 100, step: 5 },
  },
}

Range

For numeric values with a slider:
argTypes: {
  opacity: {
    control: { type: 'range', min: 0, max: 1, step: 0.1 },
  },
}

Boolean

For true/false values:
argTypes: {
  disabled: {
    control: 'boolean',
  },
}

Color

For color values with a color picker:
argTypes: {
  backgroundColor: {
    control: 'color',
  },
}

Select

For selecting one value from a dropdown:
argTypes: {
  variant: {
    control: { type: 'select' },
    options: ['primary', 'secondary', 'tertiary'],
  },
}

Radio

For selecting one value from radio buttons:
argTypes: {
  size: {
    control: { type: 'radio' },
    options: ['small', 'medium', 'large'],
  },
}

Inline Radio

For inline radio button layout:
argTypes: {
  alignment: {
    control: { type: 'inline-radio' },
    options: ['left', 'center', 'right'],
  },
}

Multi-Select

For selecting multiple values:
argTypes: {
  tags: {
    control: { type: 'multi-select' },
    options: ['urgent', 'bug', 'feature', 'enhancement'],
  },
}

Object

For object values (JSON editor):
argTypes: {
  style: {
    control: 'object',
  },
}

// Usage
args: {
  style: { padding: 10, margin: 5 },
}

Array

For array values:
argTypes: {
  items: {
    control: 'array',
  },
}

// Usage
args: {
  items: ['apple', 'banana', 'orange'],
}

Date

For date values with a date picker:
argTypes: {
  startDate: {
    control: 'date',
  },
}

Parameters

Customize Controls behavior with parameters:
controls
ControlsParameters
Controls addon configuration
sort
'alpha' | 'requiredFirst' | 'none'
default:"'none'"
How to sort controls in the panel:
  • 'alpha': Alphabetically by name
  • 'requiredFirst': Required args first, then alphabetically
  • 'none': Display in definition order
expanded
boolean
default:"false"
Whether to expand object/array controls by default
presetColors
PresetColor[]
default:"undefined"
Preset colors for the color picker
presetColors: [
  '#FF0000',
  '#00FF00',
  '#0000FF',
  { color: '#FFFF00', title: 'Yellow' },
]
hideNoControlsWarning
boolean
default:"false"
Hide the warning when no controls are defined

Examples

Complete Example

import type { Meta, StoryObj } from '@storybook/react-native';
import { View, Text, StyleSheet } from 'react-native';

interface CardProps {
  title: string;
  subtitle?: string;
  backgroundColor: string;
  elevation: number;
  rounded: boolean;
  size: 'small' | 'medium' | 'large';
}

const Card = ({ 
  title, 
  subtitle, 
  backgroundColor, 
  elevation,
  rounded,
  size,
}: CardProps) => (
  <View style={[
    styles.card,
    { 
      backgroundColor, 
      elevation,
      borderRadius: rounded ? 12 : 0,
      padding: size === 'small' ? 8 : size === 'large' ? 24 : 16,
    }
  ]}>
    <Text style={styles.title}>{title}</Text>
    {subtitle && <Text style={styles.subtitle}>{subtitle}</Text>}
  </View>
);

const meta = {
  component: Card,
  argTypes: {
    title: {
      control: 'text',
      description: 'The main heading text',
    },
    subtitle: {
      control: 'text',
      description: 'Optional secondary text',
    },
    backgroundColor: {
      control: 'color',
      description: 'Card background color',
    },
    elevation: {
      control: { type: 'range', min: 0, max: 24, step: 1 },
      description: 'Shadow elevation',
    },
    rounded: {
      control: 'boolean',
      description: 'Apply rounded corners',
    },
    size: {
      control: { type: 'radio' },
      options: ['small', 'medium', 'large'],
      description: 'Card padding size',
    },
  },
  parameters: {
    controls: {
      sort: 'requiredFirst',
      presetColors: [
        '#FFFFFF',
        '#F3F4F6',
        '#3B82F6',
        '#10B981',
        '#F59E0B',
      ],
    },
  },
} satisfies Meta<typeof Card>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Interactive: Story = {
  args: {
    title: 'Hello World',
    subtitle: 'This is a card component',
    backgroundColor: '#3B82F6',
    elevation: 4,
    rounded: true,
    size: 'medium',
  },
};

const styles = StyleSheet.create({
  card: {
    width: 300,
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    color: 'white',
  },
  subtitle: {
    fontSize: 14,
    color: 'white',
    opacity: 0.8,
    marginTop: 4,
  },
});

With Preset Colors

const meta = {
  component: ColoredBox,
  argTypes: {
    color: {
      control: 'color',
    },
  },
  parameters: {
    controls: {
      presetColors: [
        { color: '#FF6B6B', title: 'Red' },
        { color: '#4ECDC4', title: 'Teal' },
        { color: '#45B7D1', title: 'Blue' },
        { color: '#FFA07A', title: 'Orange' },
        { color: '#98D8C8', title: 'Mint' },
      ],
    },
  },
} satisfies Meta<typeof ColoredBox>;

Hiding Controls for Specific Args

Disable controls for certain args:
argTypes: {
  onClick: {
    control: false, // No control for function props
  },
  children: {
    control: false, // No control for React nodes
  },
}

Conditional Controls

Show/hide controls based on other arg values:
argTypes: {
  variant: {
    control: { type: 'select' },
    options: ['solid', 'outline', 'ghost'],
  },
  outlineWidth: {
    control: { type: 'number', min: 1, max: 5 },
    if: { arg: 'variant', eq: 'outline' }, // Only show when variant is 'outline'
  },
}

API Reference

ArgType Interface

interface ArgType {
  name?: string;
  description?: string;
  defaultValue?: any;
  control?: 
    | ControlTypes 
    | { type: ControlTypes; [key: string]: any }
    | false;
  options?: any[];
  if?: { arg: string; eq: any };
  [key: string]: any;
}

ControlTypes

type ControlTypes =
  | 'text'
  | 'number'
  | 'color'
  | 'boolean'
  | 'object'
  | 'select'
  | 'array'
  | 'date'
  | 'radio'
  | 'inline-radio'
  | 'multi-select'
  | 'range';

PresetColor

type PresetColor =
  | string
  | {
      color: string;
      title?: string;
    };

Programmatic Access

Access and update args programmatically:
import { useArgs } from '@storybook/preview-api';

function MyComponent() {
  const [args, updateArgs, resetArgs] = useArgs();

  return (
    <Button
      onPress={() => updateArgs({ count: args.count + 1 })}
      onLongPress={() => resetArgs()}
    >
      Count: {args.count}
    </Button>
  );
}

Build docs developers (and LLMs) love