Skip to content

Create Component

createComponent is the core utility that wraps any React Native component with enhanced styling capabilities: platform-specific styles, color mode support, reactive context, variants, slots, and TypeScript autocomplete.

import { createComponent } from 'react-native-small-ui';

This is part of the core package (~5.7 KB gzipped) and has no heavy dependencies.



createComponent(Component, styleObject)
createComponent(Component, config)

All React Native style properties are available as direct props. Underscore-prefixed props are context-aware:

Prop When applied
_light Light color mode
_dark Dark color mode
_ios iOS platform
_android Android platform
_web Web platform
_native iOS + Android (non-web)
_<key> Custom registered platform or color mode
import { createComponent } from 'react-native-small-ui';
import { View } from 'react-native';
const Card = createComponent(View, {
padding: 16,
borderRadius: 8,
_light: { backgroundColor: '#fff', borderColor: '#e5e5e5' },
_dark: { backgroundColor: '#1a1a1a', borderColor: '#333' },
_ios: { shadowOpacity: 0.1, shadowRadius: 4 },
_android: { elevation: 2 },
});
// Props override base styles at render time
<Card marginTop={20} borderWidth={1} />;

Pass a function instead of a style object to access the reactive context. The function runs on every render, but receives a stable ctx object — the same reference is reused when colorMode and breakpoints haven’t changed, so the output is identical and no style recalculation occurs downstream.

import { createComponent } from 'react-native-small-ui';
import { View } from 'react-native';
const Card = createComponent(View, (ctx) => ({
padding: ctx.breakpoint({ default: 8, md: 16, lg: 24 }),
backgroundColor: ctx.colorMode === 'dark' ? '#1a1a1a' : '#fff',
}));
Property Type Description
colorMode 'light' | 'dark' Current color scheme
breakpoint(values) (values) => T | undefined Resolves a responsive value map to the current breakpoint

ctx.breakpoint() uses lazy subscription — components only subscribe to the breakpoints they actually read, paying zero cost for unused breakpoints.


createComponent accepts a ComponentConfig object with variants, compoundVariants, and defaultVariants for a complete cva-style variant API. All variant props are fully type-inferred — no manual type declarations needed.

import { createComponent } from 'react-native-small-ui';
import { TouchableOpacity, Text } from 'react-native';
const Button = createComponent(TouchableOpacity, {
base: {
borderRadius: 8,
alignItems: 'center',
justifyContent: 'center',
},
variants: {
size: {
sm: { paddingVertical: 6, paddingHorizontal: 12 },
md: { paddingVertical: 10, paddingHorizontal: 20 },
lg: { paddingVertical: 14, paddingHorizontal: 28 },
},
intent: {
primary: {
_light: { backgroundColor: '#007AFF' },
_dark: { backgroundColor: '#0A84FF' },
},
danger: {
_light: { backgroundColor: '#e00c2c' },
_dark: { backgroundColor: '#be0a25' },
},
ghost: { backgroundColor: 'transparent' },
},
},
defaultVariants: {
size: 'md',
intent: 'primary',
},
});
// Variant props are autocompleted: size?, intent?
<Button size="lg" intent="danger">
<Text>Delete</Text>
</Button>
// Defaults apply — no props needed
<Button>
<Text>Submit</Text>
</Button>

Apply styles only when a specific combination of variants is active. Resolved after individual variants — compound styles always win on conflict.

const Button = createComponent(TouchableOpacity, {
variants: {
size: {
sm: { padding: 6 },
lg: { padding: 14 },
},
intent: {
danger: { borderRadius: 4 },
ghost: { borderRadius: 0 },
},
},
compoundVariants: [
{
variants: { size: 'sm', intent: 'danger' },
style: { borderWidth: 2, borderColor: '#e00c2c' }, // only sm + danger
},
],
});

Styles are merged in this order (later wins on conflict):

  1. base styles
  2. defaultVariants styles
  3. Prop-supplied variant styles
  4. compoundVariants styles (all matching entries, declaration order)
  5. Direct style props (always win)

See the Variant System Guide for full documentation.


Every createComponent output has an .extend() method for ergonomic composition. Returns a new component — the original is unchanged.

import { createComponent } from 'react-native-small-ui';
import { View } from 'react-native';
// Base component
const Box = createComponent(View, {
padding: 8,
borderRadius: 4,
});
// Extend with additional styles — base styles preserved, extension wins on conflict
const Card = Box.extend({
padding: 16, // overrides base padding
_light: { backgroundColor: '#fff' },
_dark: { backgroundColor: '#1a1a1a' },
});
// Extend with a full config — variants merged on top of base
const VariantCard = Box.extend({
variants: {
elevated: {
yes: { _ios: { shadowOpacity: 0.15 }, _android: { elevation: 4 } },
no: {},
},
},
defaultVariants: { elevated: 'yes' },
});

.extend() accepts either a plain style object or a full ComponentConfig. When extending configs, variants and defaultVariants are shallowly merged — extension values override base values on conflict.


Attach named sub-components as dot-notation properties on a parent component. Slots share reactive context (colorMode, breakpoints) implicitly — no prop drilling or providers needed.

import { createComponent } from 'react-native-small-ui';
import { View, Text } from 'react-native';
const Card = createComponent(View, {
borderRadius: 8,
overflow: 'hidden',
_light: { backgroundColor: '#fff', borderColor: '#e5e5e5' },
_dark: { backgroundColor: '#1a1a1a', borderColor: '#333' },
borderWidth: 1,
}).withSlots({
Header: createComponent(View, {
padding: 16,
borderBottomWidth: 1,
_light: { borderBottomColor: '#e5e5e5' },
_dark: { borderBottomColor: '#333' },
}),
Body: createComponent(View, { padding: 16 }),
Footer: createComponent(View, {
padding: 12,
borderTopWidth: 1,
_light: { borderTopColor: '#e5e5e5' },
_dark: { borderTopColor: '#333' },
}),
});
// Usage — all slots share colorMode with the parent automatically
function ProfileCard() {
return (
<Card>
<Card.Header>
<Text>Title</Text>
</Card.Header>
<Card.Body>
<Text>Content</Text>
</Card.Body>
<Card.Footer>
<Text>Actions</Text>
</Card.Footer>
</Card>
);
}

When slots need to reflect the parent’s active variant (not just colorMode), chain .withVariantContext() after .withSlots(). See the Button component for a complete example.


Creates a named group of sibling components that all share reactive context without a parent-child hierarchy. Useful for form systems, navigation bars, or any set of related components that need consistent styling.

import { createComponentGroup } from 'react-native-small-ui';
import { View, Text, TextInput } from 'react-native';
const { FormLabel, FormInput, FormError } = createComponentGroup({
FormLabel: {
Component: Text,
style: {
fontSize: 14,
fontWeight: '600',
marginBottom: 4,
_light: { color: '#1c1c1e' },
_dark: { color: '#fafafa' },
},
},
FormInput: {
Component: View,
style: {
borderWidth: 1,
borderRadius: 6,
padding: 10,
_light: { borderColor: '#c0a3cc', backgroundColor: '#fdfbfd' },
_dark: { borderColor: '#2d283a', backgroundColor: '#09090b' },
},
},
FormError: {
Component: Text,
style: {
fontSize: 12,
marginTop: 4,
_light: { color: '#e00c2c' },
_dark: { color: '#be0a25' },
},
},
});
// All three respond to colorMode changes without any wrapper
function EmailField() {
return (
<View>
<FormLabel>Email</FormLabel>
<FormInput>
<TextInput placeholder="you@example.com" />
</FormInput>
<FormError>This field is required</FormError>
</View>
);
}

Difference from .withSlots():

  • .withSlots() — parent-child relationship, dot-notation access from parent
  • createComponentGroup() — sibling relationship, destructured from factory call, no required parent

createComponent(Component, styleObject?, defaultProps?)
createComponent(Component, config, defaultProps?)
Field Type Description
base ComponentStyle Base styles applied to every instance
variants VariantConfig Named variant groups. Keys become typed component props.
compoundVariants CompoundVariant[] Styles applied only when a combination of variants matches
defaultVariants Partial<VariantProps> Default values for variant props
Method Signature Description
.extend() (styleOrConfig) => SmallUIComponent Create a new component inheriting this one’s styles
.withSlots() (slots) => SmallUIComponent & S Attach named sub-components as dot-notation properties
.withVariantContext() (...keys) => SmallUIComponent & S Propagate the parent’s active variant values to slots automatically via React context

Chain after .withSlots() to make the parent’s active variant values available to every slot automatically — no prop needed on the slot at the call site.

const Button = createComponent(TouchableOpacity, {
variants: { intent: { primary: {...}, ghost: {...} } },
defaultVariants: { intent: 'primary' },
}).withSlots({
Text: createComponent(Text, {
variants: { intent: { primary: { color: '#fff' }, ghost: { color: '#007AFF' } } },
defaultVariants: { intent: 'primary' },
}),
}).withVariantContext('intent');
// intent propagates — no prop on Button.Text
<Button intent="ghost">
<Button.Text>Cancel</Button.Text>
</Button>
// Explicit slot prop always overrides context
<Button intent="ghost">
<Button.Text intent="primary">...</Button.Text>
</Button>

Accepts multiple keys: .withVariantContext('intent', 'size'). Each compound component gets its own isolated context — independent instances never share state.


React Native style props can be passed dynamically at render time. To drive component appearance from theme tokens, read the active theme with useTheme and pass the values as props:

import { createComponent } from 'react-native-small-ui';
import { useTheme } from 'react-native-small-ui/theme';
import { View, Text } from 'react-native';
type AppTheme = {
light: { card: string; border: string; primary: string };
dark: { card: string; border: string; primary: string };
};
// Component defined once at module scope — always stable
const Card = createComponent(View, {
borderRadius: 12,
padding: 16,
borderWidth: 1,
});
function ProfileCard() {
const theme = useTheme() as AppTheme;
// Theme tokens become props — Card re-renders when theme changes,
// but its identity never changes so React reconciles normally
return (
<Card
_light={{ backgroundColor: theme.light.card, borderColor: theme.light.border }}
_dark={{ backgroundColor: theme.dark.card, borderColor: theme.dark.border }}
>
<Text>Content</Text>
</Card>
);
}

This pattern works across all createComponent features — variants, .extend(), .withSlots() — because the component type is stable. Theme values flow in as props the same way any other runtime value does.