Storybook UI Configuration
The getStorybookUI
function configures how Storybook renders and behaves in your React Native app. This page covers all available options and their usage.
Basic Usage
import { view } from './storybook.requires';
const StorybookUIRoot = view.getStorybookUI({
// Options go here
});
export default StorybookUIRoot;
Complete Options Reference
const StorybookUIRoot = view.getStorybookUI({
// UI Behavior
onDeviceUI: true,
shouldPersistSelection: true,
// Initial Story
initialSelection: {
kind: 'Components/Button',
name: 'Primary',
},
// Theme Customization
theme: {
// Theme options (see Theme section below)
},
// Storage Implementation
storage: {
getItem: AsyncStorage.getItem,
setItem: AsyncStorage.setItem,
},
// WebSocket Configuration
enableWebsockets: false,
host: 'localhost',
port: 7007,
// Custom UI Component
CustomUIComponent: undefined,
});
Option Details
UI Behavior Options
onDeviceUI
(boolean)
- Default:
true
- Purpose: Enable or disable the on-device UI (story navigator, addons panel)
- Use Case: Set to
false
when using only WebSocket control or custom UI
// Story-only mode (no UI controls)
const StorybookUIRoot = view.getStorybookUI({
onDeviceUI: false,
});
shouldPersistSelection
(boolean)
- Default:
true
- Purpose: Remember the last viewed story between app launches
- Storage: Requires a storage implementation
const StorybookUIRoot = view.getStorybookUI({
shouldPersistSelection: true,
storage: {
getItem: AsyncStorage.getItem,
setItem: AsyncStorage.setItem,
},
});
Initial Story Selection
initialSelection
(object | string)
- Default: First story in the index
- Purpose: Set which story displays on first launch
- Formats:
- Object:
{ kind: string, name: string }
- String:
'kind--name'
- Object:
// Object format (recommended)
const StorybookUIRoot = view.getStorybookUI({
initialSelection: {
kind: 'Components/Button',
name: 'Primary',
},
});
// String format
const StorybookUIRoot = view.getStorybookUI({
initialSelection: 'components-button--primary',
});
Theme Configuration
theme
(object)
- Default: Built-in light theme
- Purpose: Customize the appearance of Storybook UI
- Interface: Uses
StorybookTheme
interface from@storybook/react-native-theming
Using Built-in Themes
import { theme, darkTheme } from '@storybook/react-native-theming';
// Use built-in light theme
const StorybookUIRoot = view.getStorybookUI({
theme: theme,
});
// Use built-in dark theme
const StorybookUIRoot = view.getStorybookUI({
theme: darkTheme,
});
Custom Theme Structure
import { view } from './storybook.requires';
import AsyncStorage from '@react-native-async-storage/async-storage';
const StorybookUIRoot = view.getStorybookUI({
storage: {
getItem: AsyncStorage.getItem,
setItem: AsyncStorage.setItem,
},
theme: {
// Base theme type
base: 'light', // or 'dark'
// Text color for muted elements
textMutedColor: '#5C6870',
// Color palette
color: {
primary: '#FF4785',
secondary: '#029CFD',
tertiary: '#FAFBFC',
// Status colors
positive: '#66BF3C',
negative: '#FF4400',
warning: '#E69D00',
// Text colors
defaultText: '#2E3438',
inverseText: '#FFFFFF',
// Monochrome scale
lightest: '#FFFFFF',
lighter: '#F7FAFC',
light: '#EEF3F6',
medium: '#D9E8F2',
dark: '#5C6870',
darkest: '#2E3438',
},
// Background colors
background: {
app: '#F6F9FC',
bar: '#FFFFFF',
content: '#FFFFFF',
preview: '#FFFFFF',
},
// Typography settings
typography: {
weight: {
regular: '400',
bold: '700',
},
size: {
s1: 12,
s2: 14,
s3: 16,
m1: 20,
m2: 24,
m3: 28,
l1: 32,
l2: 40,
l3: 48,
},
},
// Input field styling
input: {
background: '#FFFFFF',
border: 'hsla(203, 50%, 30%, 0.15)',
borderRadius: 4,
color: '#2E3438',
paddingHorizontal: 10,
paddingVertical: 6,
},
// Button styling
button: {
background: '#F6F9FC',
border: '#D9E8F2',
},
// Boolean control styling
boolean: {
background: '#ECF4F9',
selectedBackground: '#FFFFFF',
},
// Layout and borders
layoutMargin: 10,
appBorderColor: 'hsla(203, 50%, 30%, 0.15)',
appBorderRadius: 4,
// Toolbar colors
barTextColor: '#73828C',
barHoverColor: '#029CFD',
barSelectedColor: '#029CFD',
barBg: '#FFFFFF',
// Brand customization
brand: {
title: 'My App Storybook',
url: 'https://myapp.com',
image: require('./logo.png'), // or URI
},
},
});
export default StorybookUIRoot;
Storage Configuration
storage
(object)
- Default: No storage (selection not persisted)
- Purpose: Persist UI state between sessions
- Interface:
interface Storage {
getItem: (key: string) => Promise<string | null>;
setItem: (key: string, value: string) => Promise<void>;
}
AsyncStorage (Most Common)
import { view } from './storybook.requires';
import AsyncStorage from '@react-native-async-storage/async-storage';
const StorybookUIRoot = view.getStorybookUI({
storage: {
getItem: AsyncStorage.getItem,
setItem: AsyncStorage.setItem,
},
});
export default StorybookUIRoot;
MMKV (High Performance)
If you prefer using MMKV for better performance:
import { view } from './storybook.requires';
import { MMKV } from 'react-native-mmkv';
const storage = new MMKV();
const StorybookUIRoot = view.getStorybookUI({
storage: {
getItem: async (key) => storage.getString(key) ?? null,
setItem: async (key, value) => storage.set(key, value),
},
});
export default StorybookUIRoot;
WebSocket Options
Enable remote control of Storybook from external tools:
enableWebsockets
(boolean)
- Default:
false
- Purpose: Enable WebSocket server for remote control
host
(string)
- Default:
'localhost'
- Purpose: WebSocket server hostname
- Note: Use your machine's IP for physical devices
port
(number)
- Default:
7007
- Purpose: WebSocket server port
const StorybookUIRoot = view.getStorybookUI({
enableWebsockets: true,
host: '192.168.1.100', // Your machine's IP
port: 7007,
});
Custom UI Component
CustomUIComponent
(React Component)
- Default:
undefined
(uses built-in UI) - Purpose: Replace default UI with custom implementation
- Interface: Must implement the
SBUI
interface
The CustomUIComponent
option allows you to completely replace Storybook's default UI with your own implementation. This is useful when you want to integrate Storybook into an unsupported platform or create a completely custom story browsing experience.
Interface Requirements
Your custom UI component must implement the SBUI
interface:
type SBUI = (props: {
story?: StoryContext<ReactRenderer, Args>;
storyHash: API_IndexHash;
setStory: (storyId: string) => void;
storage: Storage;
theme: Theme;
children: ReactElement;
}) => ReactElement;
Props Explanation
story
- The currently selected story context with metadata and parametersstoryHash
- Hash map of all available stories indexed by story IDsetStory
- Function to programmatically navigate to a story by IDstorage
- Storage interface for persisting data (typically AsyncStorage)theme
- Theme object from@storybook/react-native-theming
children
- The actual story content that must be rendered
Practical Example
This example shows a basic custom UI with a modal-based story selector:
import AsyncStorage from '@react-native-async-storage/async-storage';
import {
Button,
Modal,
SafeAreaView,
ScrollView,
Text,
TouchableOpacity,
View,
} from 'react-native';
import { view } from './storybook.requires';
import { SBUI } from '@storybook/react-native-ui-common';
import { useMemo, useState } from 'react';
const MyCustomUI: SBUI = ({ story, storyHash, setStory, children }) => {
const stories = useMemo(() => Object.values(storyHash), [storyHash]);
const [showModal, setShowModal] = useState(false);
return (
<SafeAreaView style={{ flex: 1, flexDirection: 'column' }}>
{/* Story Navigation Modal */}
<Modal
visible={showModal}
onRequestClose={() => setShowModal(false)}
animationType="slide"
>
<SafeAreaView style={{ flex: 1, backgroundColor: '#fff' }}>
<ScrollView style={{ backgroundColor: '#f5f5f5' }}>
<Text style={{ fontSize: 18, padding: 16 }}>Stories</Text>
{stories.map((item) => {
if (item.type !== 'story') {
// Handle non-story items (groups, docs, etc.)
return null;
}
return (
<TouchableOpacity
key={item.id}
onPress={() => {
setStory(item.id);
setShowModal(false);
}}
style={{
padding: 12,
backgroundColor: story?.id === item.id ? '#007AFF' : 'transparent',
}}
>
<Text
style={{
color: story?.id === item.id ? 'white' : 'black',
}}
>
{item.name}
</Text>
</TouchableOpacity>
);
})}
</ScrollView>
</SafeAreaView>
</Modal>
{/* Story Content */}
<View style={{ flex: 1 }}>{children}</View>
{/* Story Navigation Button */}
<Button title="Show Stories" onPress={() => setShowModal(true)} />
</SafeAreaView>
);
};
const StorybookUIRoot = view.getStorybookUI({
CustomUIComponent: MyCustomUI,
storage: {
getItem: AsyncStorage.getItem,
setItem: AsyncStorage.setItem,
},
});
export default StorybookUIRoot;
Alternative Approaches
Using Lite UI Components
You can also use the CustomUIComponent property to pass the lite ui for a ui that requires less dependencies and is more compatible with other platforms.
import { view } from './storybook.requires';
import { LiteUI } from '@storybook/react-native-ui-lite';
const StorybookUIRoot = view.getStorybookUI({
CustomUIComponent: LiteUI, // Lightweight alternative to full UI
// ... other options
});
export default StorybookUIRoot;
Conditional Custom UI
You can conditionally use custom UI based on environment or user preferences:
import { view } from './storybook.requires';
const StorybookUIRoot = view.getStorybookUI({
CustomUIComponent: Platform.OS === 'windows' ? MyCustomUI : undefined,
// ... other options
});
export default StorybookUIRoot;
Important Notes
- The
children
prop contains the rendered story and should be included in your UI - When
CustomUIComponent
is provided, it completely replaces the default Storybook UI - Your component receives the same props as the built-in UI components
- You can reference the built-in implementations in
@storybook/react-native-ui
and@storybook/react-native-ui-lite
for inspiration - The custom UI is responsible for all navigation and story selection logic
- If onDeviceUI is disabled only the story will render even if the custom UI is provided
Common Configurations
Standard Setup
import { view } from './storybook.requires';
import AsyncStorage from '@react-native-async-storage/async-storage';
const StorybookUIRoot = view.getStorybookUI({
storage: {
getItem: AsyncStorage.getItem,
setItem: AsyncStorage.setItem,
},
});
export default StorybookUIRoot;
Testing Setup
import { view } from './storybook.requires';
import AsyncStorage from '@react-native-async-storage/async-storage';
const StorybookUIRoot = view.getStorybookUI({
onDeviceUI: false, // No UI for automated tests
enableWebsockets: true,
host: 'localhost', // use websocket server to control storybook
port: 7007,
storage: {
getItem: AsyncStorage.getItem,
setItem: AsyncStorage.setItem,
},
});
export default StorybookUIRoot;