Complete guide for using the Kor UI library (@korsolutions/ui) in React Native and Expo applications. Use this skill when building user interfaces with Universal UI, customizing themes, setting up the library, working with any of the 27+ components (Button, IconButton, Input, Select, Checkbox, RadioGroup, Alert, Card, Separator, Tabs, Menu, Popover, Calendar, Toast, etc.), styling and theming, implementing compound components, debugging component issues, or when the user mentions "@korsolutions/ui", "Universal UI", "UIProvider", or asks about unstyled primitives, theme customization, or React Native UI components. This skill covers installation, provider setup, component usage patterns, theme customization, variant system, hooks, responsive design, and troubleshooting.
Resources
1Install
npx skillscat add korsoftwaresolutions/ui/kor-ui Install via the SkillsCat registry.
Universal UI Library
Universal UI (@korsolutions/ui) is a library of unstyled UI primitives for React Native and Expo applications. It provides production-ready components with a focus on minimal dependencies, compound component patterns, and comprehensive theming support.
Core Principles
- Unstyled Primitives: Components are unstyled by default with variant-based styling
- Compound Components: All components follow Root + sub-component pattern
- Variant System: Each component offers multiple style variants
- Minimal Dependencies: Only React Native and Expo core dependencies
- Full TypeScript Support: Complete type definitions for all components
- Cross-Platform: iOS, Android, and Web support
Quick Start
Installation
npm install @korsolutions/ui
# or
yarn add @korsolutions/ui
# or
bun add @korsolutions/uiProvider Setup
Wrap your application with UIProvider in your root layout:
import { UIProvider } from "@korsolutions/ui";
import { useSafeAreaInsets } from "react-native-safe-area-context";
export default function RootLayout() {
const safeAreaInsets = useSafeAreaInsets();
return (
<UIProvider safeAreaInsets={safeAreaInsets}>
<YourApp />
</UIProvider>
);
}Basic Import Pattern
import { Button, Input, Card } from "@korsolutions/ui";
function MyComponent() {
return (
<Card.Root>
<Card.Body>
<Button onPress={() => console.log("Pressed")}>
Click Me
</Button>
</Card.Body>
</Card.Root>
);
}Your First Component
import { useState } from "react";
import { Button } from "@korsolutions/ui";
function SubmitButton() {
const [loading, setLoading] = useState(false);
const handleSubmit = async () => {
setLoading(true);
await submitForm();
setLoading(false);
};
return (
<Button variant="default" isLoading={loading} onPress={handleSubmit}>
Submit
</Button>
);
}Component Overview
Layout & Structure
| Component | Description | Variants | Reference |
|---|---|---|---|
| Card | Content container with header, body, and footer | default | Layout Components |
| Separator | Visual divider between content | horizontal, vertical | Layout Components |
| Portal | Render components outside hierarchy | - | Layout Components |
| List | Performance-optimized list rendering | - | Layout Components |
Form Inputs
| Component | Description | Variants | Reference |
|---|---|---|---|
| Input | Text input field | default | Input Components |
| NumericInput | Formatted numeric input (currency, percentage, etc.) | default | Input Components |
| PhoneInput | Phone number input with country selector (E.164) | default | Input Components |
| Textarea | Multi-line text input | default | Input Components |
| Checkbox | Toggle selection with label | default, outlined | Input Components |
| RadioGroup | Single selection from a group of options | default, outlined | Input Components |
| Select | Dropdown selection from a list of options | default | Input Components |
| Combobox | Autocomplete input with dropdown (consumer-side filtering) | default | Input Components |
| Field | Form field wrapper with label and validation | - | Input Components |
Display Components
| Component | Description | Variants | Reference |
|---|---|---|---|
| Typography | Text with semantic variants | heading-xs to 3xl, body-xs to lg, label, code, caption | Display Components |
| Avatar | User avatar with image and fallback | default | Display Components |
| Badge | Status indicators and labels | default, secondary, success, warning, danger, info | Display Components |
| Icon | Icon rendering with render prop pattern | - | Display Components |
| Empty | Empty state placeholders | default | Display Components |
| Progress | Linear progress indicators | default | Display Components |
Interactive Components
| Component | Description | Variants | Reference |
|---|---|---|---|
| Button | Action buttons with loading states | default, secondary, ghost | Interactive Components |
| IconButton | Icon-only pressable button | default, secondary, ghost | Interactive Components |
| Tabs | Tabbed navigation | default, line | Interactive Components |
| Menu | Dropdown menus | default | Interactive Components |
| Popover | Positioned overlay content | default | Interactive Components |
| Calendar | Month date picker (compound) | default | Interactive Components |
| WeekCalendar | Swipeable week strip with date selection | default | Interactive Components |
| CalendarTimeline | Day timeline with generic event rendering | default | Interactive Components |
Feedback Components
| Component | Description | Variants | Reference |
|---|---|---|---|
| Alert | Inline notifications with icons | default, destructive | Feedback Components |
| AlertDialog | Modal confirmation dialogs | default | Feedback Components |
| Toast | Transient notifications | default, success, danger | Feedback Components |
Compound Component Pattern
All Universal UI components follow a compound component pattern where a parent component (usually Root) provides context to child sub-components.
Structure
<Component.Root {...rootProps}>
<Component.SubComponent1 {...props} />
<Component.SubComponent2 {...props} />
</Component.Root>Common Sub-Components
Most components share similar sub-component naming:
- Root - Parent container that provides context
- Label - Text label for the component
- Icon - Icon display with render prop pattern
- Description - Secondary descriptive text
- Title - Primary heading text
- Body - Main content area
- Header - Top section
- Footer - Bottom section
Example: Button
<Button variant="default" onPress={handlePress} isLoading={loading}>
Submit Form
</Button>Example: Alert with Icon
import { AlertCircle } from "lucide-react-native";
<Alert.Root variant="destructive">
<Alert.Icon render={AlertCircle} />
<Alert.Body>
<Alert.Title>Error</Alert.Title>
<Alert.Description>Something went wrong</Alert.Description>
</Alert.Body>
</Alert.Root>;Example: Field with Input
<Field.Root>
<Field.Label for="email">Email Address</Field.Label>
<Input id="email" value={email} onChange={setEmail} placeholder="you@example.com" />
<Field.Description>We'll never share your email.</Field.Description>
{error && <Field.Error>{error}</Field.Error>}
</Field.Root>Style Composition
Component styles are always composed with variant styles first, allowing user styles to override:
// Variant styles are applied first
<Button style={{ marginTop: 16 }}>
Custom Button
</Button>This ensures your custom styles always take precedence over variant defaults.
Theme System Basics
Universal UI includes a comprehensive theming system with light/dark mode support.
Theme Tokens
The theme provides these customizable tokens:
- colors - Color palette with light/dark schemes
- radius - Border radius (default: 10)
- fontSize - Base font size (default: 16)
- fontFamily - Font family (default: "System")
- letterSpacing - Letter spacing (default: 0)
Color Tokens
Each color scheme (light/dark) includes:
- background - Main background color
- foreground - Main text color
- primary - Primary brand color
- primaryForeground - Text on primary color
- secondary - Secondary brand color
- secondaryForeground - Text on secondary color
- muted - Muted background color
- mutedForeground - Muted text color
- border - Border color
- surface - Surface/card background
- success, warning, danger, info - Semantic colors
Using the Theme
Access the theme in your components:
import { useTheme } from "@korsolutions/ui";
function MyComponent() {
const theme = useTheme();
return (
<View
style={{
backgroundColor: theme.colors.background,
borderRadius: theme.radius,
padding: 16,
}}
>
<Text
style={{
color: theme.colors.foreground,
fontSize: theme.fontSize,
fontFamily: theme.fontFamily,
}}
>
Themed Content
</Text>
</View>
);
}Color Scheme
Toggle between light and dark mode:
const theme = useTheme();
// Get current scheme
console.log(theme.colorScheme); // "light" | "dark"
// Set color scheme
theme.setColorScheme("dark");Quick Customization
Customize the theme via UIProvider:
<UIProvider
theme={{
radius: 12,
fontSize: 18,
colors: {
light: {
primary: "hsla(220, 90%, 56%, 1)",
primaryForeground: "hsla(0, 0%, 100%, 1)",
},
dark: {
primary: "hsla(220, 90%, 70%, 1)",
primaryForeground: "hsla(0, 0%, 100%, 1)",
},
},
}}
safeAreaInsets={safeAreaInsets}
>
<App />
</UIProvider>For detailed theming documentation, see Theme Customization.
Common Patterns
Form Field with Validation
import { Field, Input } from "@korsolutions/ui";
<Field.Root>
<Field.Label for="email">Email</Field.Label>
<Input id="email" value={email} onChange={setEmail} placeholder="you@example.com" />
<Field.Description>Enter your email address</Field.Description>
{error && <Field.Error>{error}</Field.Error>}
</Field.Root>;Icons with Render Prop
Universal UI uses a render prop pattern for icons, supporting any icon library:
import { AlertCircle, CheckCircle } from "lucide-react-native";
import { Alert } from "@korsolutions/ui";
// With lucide-react-native
<Alert.Icon render={AlertCircle} />
// With custom function
<Alert.Icon render={(props) => <CheckCircle {...props} size={20} />} />
// With @expo/vector-icons
import { MaterialCommunityIcons } from "@expo/vector-icons";
<Alert.Icon render={(props) => (
<MaterialCommunityIcons {...props} name="alert-circle" />
)} />Icon Button
A pressable button that renders a single icon. Uses the same render prop pattern as Icon:
import { IconButton } from "@korsolutions/ui";
import { Heart, Settings, Trash } from "lucide-react-native";
// Basic usage
<IconButton render={Heart} onPress={() => console.log("Liked")} />
// Variants (matches Button variants)
<IconButton render={Settings} variant="secondary" />
<IconButton render={Settings} variant="ghost" />
// Custom size and color
<IconButton render={Trash} size={32} color="red" />
// Disabled
<IconButton render={Heart} isDisabled />Separator
A visual divider between content sections:
import { Separator } from "@korsolutions/ui";
// Horizontal (default)
<Separator />
// Vertical
<Separator variant="vertical" />Controlled State Management
Most input components use controlled state:
import { useState } from "react";
import { Input, Checkbox } from "@korsolutions/ui";
function Form() {
const [text, setText] = useState("");
const [checked, setChecked] = useState(false);
return (
<>
<Input value={text} onChange={setText} />
<Checkbox.Root checked={checked} onChange={setChecked}>
<Checkbox.Indicator />
<Checkbox.Content>
<Checkbox.Title>Accept terms</Checkbox.Title>
</Checkbox.Content>
</Checkbox.Root>
</>
);
}Loading States
Buttons support loading states with built-in spinner:
<Button isLoading={isSubmitting} onPress={handleSubmit}>
Submit
</Button>When isLoading is true, the button displays ActivityIndicator and disables interaction.
Disabled States
Most components support disabled states:
<Button isDisabled={!formValid} onPress={handleSubmit}>
Submit
</Button>
<Input isDisabled value={email} onChange={setEmail} />
<Checkbox.Root disabled checked={value} onChange={setValue}>
<Checkbox.Indicator />
<Checkbox.Content>
<Checkbox.Title>Disabled option</Checkbox.Title>
</Checkbox.Content>
</Checkbox.Root>Selecting Variants
Most components offer multiple variants:
// Button variants
<Button variant="default">
Default Button
</Button>
<Button variant="secondary">
Secondary Button
</Button>
<Button variant="ghost">
Ghost Button
</Button>
// Alert variants
<Alert.Root variant="default">
<Alert.Body>
<Alert.Title>Info</Alert.Title>
</Alert.Body>
</Alert.Root>
<Alert.Root variant="destructive">
<Alert.Body>
<Alert.Title>Error</Alert.Title>
</Alert.Body>
</Alert.Root>
// Badge variants
<Badge variant="success">Active</Badge>
<Badge variant="danger">Inactive</Badge>
<Badge variant="warning">Pending</Badge>Style Overrides
Override component styles using the style prop:
<Button
style={{
marginTop: 20,
backgroundColor: "blue",
}}
>
Custom Styled
</Button>Import Reference
Component Imports
// Import individual components
import { Button, Input, Card, Alert } from "@korsolutions/ui";
// Import all components
import * as UI from "@korsolutions/ui";Hook Imports
// Theme hook
import { useTheme } from "@korsolutions/ui";
// Responsive design hook
import { useScreenSize } from "@korsolutions/ui";
// React Navigation theme integration
import { useReactNavigationTheme } from "@korsolutions/ui";Provider Import
import { UIProvider } from "@korsolutions/ui";Type Imports
// Component prop types
import type { ButtonProps } from "@korsolutions/ui";
import type { InputProps } from "@korsolutions/ui";
// Theme types
import type { ThemeAssets, Colors } from "@korsolutions/ui";Quick Troubleshooting
Provider Not Wrapping App
Issue: Components don't render or theme doesn't apply
Solution: Ensure UIProvider wraps your app in the root layout:
// app/_layout.tsx
import { UIProvider } from "@korsolutions/ui";
export default function RootLayout() {
return (
<UIProvider>
<Stack />
</UIProvider>
);
}Import Errors
Issue: Cannot resolve @korsolutions/ui
Solution: Install the package and restart your bundler:
npm install @korsolutions/ui
# Restart Metro bundlerTheme Not Updating
Issue: Theme changes don't reflect in components
Solution: Ensure theme customization is passed to UIProvider before app renders:
const customTheme = {
colors: { light: { primary: "hsla(220, 90%, 56%, 1)" } },
};
<UIProvider theme={customTheme}>
<App />
</UIProvider>;Styles Not Applying
Issue: Custom styles don't override component styles
Solution: Remember style composition order - user styles always override variant styles:
// This works - style prop overrides variant
<Button style={{ backgroundColor: "red" }}>
Red Button
</Button>For comprehensive troubleshooting, see Troubleshooting Guide.
Reference Documentation
Consult these detailed references as needed:
Component References
- Layout Components - Card, Separator, Portal, List
- Input Components - Input, NumericInput, Textarea, Checkbox, RadioGroup, Select, Combobox, Field
- Display Components - Typography, Avatar, Badge, Icon, Empty, Progress
- Interactive Components - Button, Tabs, Menu, Popover, Calendar
- Feedback Components - Alert, AlertDialog, Toast
System References
- Theme Customization - Complete theming guide with color schemes, typography, and responsive design
- Patterns & Recipes - Common implementation patterns for forms, modals, navigation, and feedback
- Troubleshooting - Solutions for setup, component, type, and platform-specific issues