Create Component
Overview
Section titled “Overview”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.
Installation
Section titled “Installation”import { createComponent } from 'react-native-small-ui';This is part of the core package (~5.7 KB gzipped) and has no heavy dependencies.
Basic Usage
Section titled “Basic Usage”createComponent(Component, styleObject)createComponent(Component, config)Style Props
Section titled “Style Props”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} />;Reactive Style Factory (ctx)
Section titled “Reactive Style Factory (ctx)”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',}));ctx properties
Section titled “ctx properties”| 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.
Variant System
Section titled “Variant System”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>Compound Variants
Section titled “Compound Variants”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 }, ],});Resolution Order
Section titled “Resolution Order”Styles are merged in this order (later wins on conflict):
basestylesdefaultVariantsstyles- Prop-supplied variant styles
compoundVariantsstyles (all matching entries, declaration order)- Direct style props (always win)
See the Variant System Guide for full documentation.
.extend()
Section titled “.extend()”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 componentconst Box = createComponent(View, { padding: 8, borderRadius: 4,});
// Extend with additional styles — base styles preserved, extension wins on conflictconst Card = Box.extend({ padding: 16, // overrides base padding _light: { backgroundColor: '#fff' }, _dark: { backgroundColor: '#1a1a1a' },});
// Extend with a full config — variants merged on top of baseconst 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.
.withSlots()
Section titled “.withSlots()”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 automaticallyfunction 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.
createComponentGroup()
Section titled “createComponentGroup()”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 wrapperfunction 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 parentcreateComponentGroup()— sibling relationship, destructured from factory call, no required parent
Full API Reference
Section titled “Full API Reference”createComponent(Component, styleObject?, defaultProps?)createComponent(Component, config, defaultProps?)ComponentConfig
Section titled “ComponentConfig”| 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 |
SmallUIComponent methods
Section titled “SmallUIComponent methods”| 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 |
.withVariantContext()
Section titled “.withVariantContext()”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.
Theme-Driven Components
Section titled “Theme-Driven Components”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 stableconst 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.