Unistyles Adapter for Portable Content

A React Native adapter library that integrates Unistyles 3.0 with the @portable-content/sdk for seamless cross-platform styling.
Overview
This adapter provides a bridge between Portable Content's design system and Unistyles 3.0's powerful styling engine, enabling:
- Zero re-renders: Leverages Unistyles 3.0's C++ core for direct Shadow Tree updates
- Cross-platform consistency: Share up to 100% of styles across iOS, Android, and Web
- New Architecture ready: Built for React Native's Fabric renderer
- Type-safe styling: Full TypeScript support with theme and breakpoint autocomplete
- Adaptive theming: Automatic light/dark mode switching
- Performance optimized: Selective style updates based on dependencies
Requirements
React Native & Architecture
- React Native: 0.78.0+ (required for Fabric integration)
- New Architecture: Must be enabled (no Old Architecture support)
- Expo SDK: 53+ (if using Expo)
Platform Support
- iOS: 15.0+
- Android: API 7+
- Web: Full React Native Web support
- SSR: Next.js Server Side Rendering support
Development Tools
- Xcode: 16+ (recommended 16.3+) - Required by Nitro Modules
- TypeScript: 5.0+
- Node.js: 18+
Quick Start
1. Installation
npm install @portable-content/unistyles-adapter @portable-content/sdk react-native-unistyles react-native-nitro-modules react-native-edge-to-edge
yarn add @portable-content/unistyles-adapter @portable-content/sdk react-native-unistyles react-native-nitro-modules react-native-edge-to-edge
2. Babel Configuration
Add the Unistyles Babel plugin to your babel.config.js
:
module.exports = function (api) {
api.cache(true);
return {
presets: ['module:@react-native/babel-preset'],
plugins: [
[
'react-native-unistyles/plugin',
{
root: 'src',
},
],
],
};
};
3. Platform Setup
For Expo Projects
npx expo prebuild --clean
For Bare React Native
cd ios && pod install
4. Configure the Adapter
Create a styles/unistyles.ts
file in your project:
import { StyleSheet } from 'react-native-unistyles';
import { createPortableContentAdapter } from '@portable-content/unistyles-adapter';
const lightTheme = {
colors: {
primary: '#007AFF',
secondary: '#5856D6',
background: '#FFFFFF',
surface: '#F2F2F7',
text: '#000000',
textSecondary: '#8E8E93',
},
spacing: {
xs: 4,
sm: 8,
md: 16,
lg: 24,
xl: 32,
},
typography: {
fontSize: {
sm: 14,
md: 16,
lg: 18,
xl: 24,
},
},
};
const darkTheme = {
colors: {
primary: '#0A84FF',
secondary: '#5E5CE6',
background: '#000000',
surface: '#1C1C1E',
text: '#FFFFFF',
textSecondary: '#8E8E93',
},
spacing: lightTheme.spacing,
typography: lightTheme.typography,
};
const breakpoints = {
xs: 0,
sm: 576,
md: 768,
lg: 992,
xl: 1200,
};
const adapter = createPortableContentAdapter({
themes: {
light: lightTheme,
dark: darkTheme,
},
breakpoints,
});
StyleSheet.configure({
themes: adapter.themes,
breakpoints: adapter.breakpoints,
settings: {
adaptiveThemes: true,
initialTheme: 'light',
},
});
type AppThemes = typeof adapter.themes;
type AppBreakpoints = typeof adapter.breakpoints;
declare module 'react-native-unistyles' {
export interface UnistylesThemes extends AppThemes {}
export interface UnistylesBreakpoints extends AppBreakpoints {}
}
export { adapter };
5. Import Configuration
Import your configuration file in your app's entry point (e.g., App.tsx
or index.js
):
import './styles/unistyles'
import { StyleSheet } from 'react-native-unistyles'
const App = () => {
return (
<View style={styles.container}>
<Text style={styles.title}>Hello Unistyles!</Text>
</View>
)
}
const styles = StyleSheet.create(theme => ({
container: {
flex: 1,
backgroundColor: theme.colors.background,
justifyContent: 'center',
alignItems: 'center'
},
title: {
fontSize: theme.typography.fontSize.xl,
color: theme.colors.text,
fontWeight: 'bold'
}
}))
export default App
📚 Documentation
🚀 Getting Started
📖 Additional Resources
Development Setup
This section is for contributors who want to work on the adapter itself.
Prerequisites
- Node.js 18+
- Yarn or npm
- React Native development environment
- Xcode 16+ (for iOS development)
- Android Studio (for Android development)
Clone and Setup
git clone https://github.com/portable-content/unistyles-adapter.git
cd unistyles-adapter
yarn install
yarn build
yarn test
yarn lint
yarn format
Project Structure
unistyles-adapter/
├── src/ # Source code
│ ├── adapter/ # Core adapter implementation
│ ├── types/ # TypeScript type definitions
│ ├── utils/ # Utility functions
│ └── index.ts # Main export file
├── example/ # Example React Native app
│ ├── src/
│ ├── ios/
│ ├── android/
│ └── package.json
├── __tests__/ # Test files
├── docs/ # Documentation
├── .github/ # GitHub workflows
├── package.json
├── tsconfig.json
├── babel.config.js
├── jest.config.js
├── .eslintrc.js
├── .prettierrc
└── README.md
Available Scripts
yarn dev
yarn build
yarn build:watch
yarn test
yarn test:watch
yarn test:coverage
yarn lint
yarn lint:fix
yarn format
yarn typecheck
yarn example:install
yarn example:ios
yarn example:android
yarn example:web
yarn release
yarn publish
Testing
The library uses Jest for testing with React Native Testing Library:
yarn test
yarn test:watch
yarn test:coverage
yarn test src/adapter/__tests__/adapter.test.ts
Example App
The example app demonstrates the adapter's capabilities:
cd example
yarn install
yarn ios
yarn android
yarn web
API Reference
createPortableContentAdapter(config)
Creates an adapter instance that bridges Portable Content design tokens with Unistyles.
Parameters
config.themes
- Object containing theme definitions
config.breakpoints
- Object defining responsive breakpoints
config.settings
- Optional Unistyles settings
Returns
An adapter object with:
themes
- Processed themes for Unistyles
breakpoints
- Processed breakpoints for Unistyles
utils
- Utility functions for theme manipulation
Theme Structure
Themes should follow the Portable Content design token structure:
interface Theme {
colors: {
primary: string;
secondary: string;
background: string;
surface: string;
text: string;
textSecondary: string;
};
spacing: {
xs: number;
sm: number;
md: number;
lg: number;
xl: number;
};
typography: {
fontSize: {
sm: number;
md: number;
lg: number;
xl: number;
};
};
}
Advanced Usage
Custom Theme Extensions
import { createPortableContentAdapter } from '@portable-content/unistyles-adapter';
const adapter = createPortableContentAdapter({
themes: {
light: {
custom: {
gradients: {
primary: ['#FF6B6B', '#4ECDC4'],
secondary: ['#A8E6CF', '#DCEDC1'],
},
},
},
},
});
Responsive Design
const styles = StyleSheet.create((theme) => ({
container: {
padding: {
xs: theme.spacing.sm,
md: theme.spacing.lg,
xl: theme.spacing.xl,
},
fontSize: {
xs: theme.typography.fontSize.sm,
md: theme.typography.fontSize.md,
lg: theme.typography.fontSize.lg,
},
},
}));
Dynamic Theming
import { UnistylesRuntime } from 'react-native-unistyles';
UnistylesRuntime.setTheme('dark');
const currentTheme = UnistylesRuntime.themeName;
UnistylesRuntime.addPlugin((name) => {
console.log('Theme changed to:', name);
});
Troubleshooting
Common Issues
1. "Unistyles: we detected style object with N unistyles styles"
This warning occurs when spreading styles. Use array syntax instead:
<View style={{...style1, ...style2}} />
<View style={[style1, style2]} />
2. "ld.lld: error: Undefined symbols margelo::nitro::*"
Clear Android build cache:
cd android
./gradlew clean
git clean -dfX
3. Babel plugin not processing files
Ensure your babel.config.js
includes the correct root path:
plugins: [
[
'react-native-unistyles/plugin',
{
root: 'src',
},
],
];
Getting Help
Contributing
We welcome contributions! Please see our Contributing Guide for details.
Development Workflow
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature
- Make your changes
- Add tests for your changes
- Run the test suite:
yarn test
- Run linting:
yarn lint
- Commit your changes:
git commit -m 'Add amazing feature'
- Push to the branch:
git push origin feature/amazing-feature
- Open a Pull Request
License
MIT © Portable Content
Acknowledgments