Styling
Vue Native uses Yoga Flexbox on iOS and FlexboxLayout on Android — the same mental model as CSS Flexbox.
createStyleSheet
Use createStyleSheet to define styles as typed objects. Styles are validated and frozen for performance:
import { createStyleSheet } from '@thelacanians/vue-native-runtime'
const styles = createStyleSheet({
container: {
flex: 1,
flexDirection: 'column',
backgroundColor: '#F5F5F5',
padding: 16,
gap: 12,
},
card: {
backgroundColor: '#FFFFFF',
borderRadius: 12,
padding: 16,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
title: {
fontSize: 20,
fontWeight: '600',
color: '#1A1A1A',
},
})
Inline styles
You can also pass style objects directly:
<VView :style="{ flex: 1, padding: 16 }">
<VText :style="{ fontSize: 16, color: '#333' }">Hello</VText>
</VView>
Units
All numeric values are in density-independent points (dp):
| Platform | 1 dp equals |
|---|---|
| iOS @2x (iPhone SE, 8) | 2 physical pixels |
| iOS @3x (iPhone 12+) | 3 physical pixels |
| Android mdpi (160 dpi) | 1 physical pixel |
| Android xxhdpi (480 dpi) | 3 physical pixels |
The framework automatically converts dp to pixels using the device's scale factor. You don't need to handle retina/density differences.
// 16 dp ≈ 16 CSS pixels ≈ 32-48 physical pixels depending on device
{ padding: 16, fontSize: 16, borderWidth: 1 }
Percentage values
Some layout properties accept percentage strings relative to the parent's dimension:
{
width: '50%', // 50% of parent's width
height: '100%', // 100% of parent's height
maxWidth: '75%', // At most 75% of parent's width
minHeight: '25%', // At least 25% of parent's height
}
Properties supporting percentages: width, height, minWidth, minHeight, maxWidth, maxHeight, flexBasis, top, right, bottom, left.
Note: Type definitions require casting for percentages on some properties: maxWidth: '75%' as any. This will be improved in a future release.
Breaking change in v0.8.0
padding and margin (and their padding* / margin* variants) are now numbers only — percentage strings are no longer accepted. If you previously used a percentage, compute the value from the parent dimension yourself (for example with useDimensions).
Color formats
Colors are specified as strings. Supported formats:
| Format | Example | Notes |
|---|---|---|
| Hex (6-digit) | '#FF5733' | RGB |
| Hex (8-digit) | '#FF573380' | RGBA (last 2 digits = alpha) |
| Hex (3-digit) | '#F53' | Shorthand RGB |
rgb() | 'rgb(255, 87, 51)' | |
rgba() | 'rgba(255, 87, 51, 0.5)' | Alpha 0–1 |
| Named | 'red', 'blue', 'transparent' | CSS named colors |
{
backgroundColor: '#007AFF',
color: 'rgba(0, 0, 0, 0.87)',
borderColor: 'transparent',
}
Supported properties
Layout (Flexbox)
| Property | Values |
|---|---|
flex | number |
flexDirection | 'row' | 'column' | 'row-reverse' | 'column-reverse' |
flexWrap | 'wrap' | 'nowrap' |
flexGrow | number |
flexShrink | number |
flexBasis | number or 'auto' |
alignItems | 'flex-start' | 'center' | 'flex-end' | 'stretch' | 'baseline' |
alignSelf | same as alignItems | 'auto' |
alignContent | 'flex-start' | 'center' | 'flex-end' | 'stretch' | 'space-between' | 'space-around' |
justifyContent | 'flex-start' | 'center' | 'flex-end' | 'space-between' | 'space-around' | 'space-evenly' |
width, height | number (dp) or '50%' |
minWidth, minHeight | number or 'auto' |
maxWidth, maxHeight | number or percentage string |
aspectRatio | number (e.g. 1 for square, 16/9 for widescreen) |
position | 'relative' (default) | 'absolute' |
top, right, bottom, left | number |
padding, paddingHorizontal, paddingVertical, paddingTop, paddingBottom, paddingLeft, paddingRight | number |
paddingStart, paddingEnd | number (RTL-aware: map to left/right based on direction) |
margin, marginHorizontal, marginVertical, marginTop, marginBottom, marginLeft, marginRight | number |
marginStart, marginEnd | number (RTL-aware: map to left/right based on direction) |
gap, rowGap, columnGap | number |
display | 'flex' | 'none' |
overflow | 'hidden' | 'visible' |
direction | 'ltr' | 'rtl' | 'inherit' |
Removed style properties (v0.8.0)
borderStyle, textDecorationStyle, and textDecorationColor were removed — they were never implemented by the native renderers. overflow: 'scroll' was also removed; overflow only accepts 'visible' or 'hidden'. For scrollable content use <VScrollView> or a list component.
Appearance
| Property | Values |
|---|---|
backgroundColor | color string |
opacity | 0–1 |
borderRadius, borderTopLeftRadius, borderTopRightRadius, borderBottomLeftRadius, borderBottomRightRadius | number |
borderWidth, borderTopWidth, borderRightWidth, borderBottomWidth, borderLeftWidth | number |
borderColor | color string |
overflow | 'hidden' | 'visible' |
zIndex | number |
transform | array of transform objects |
Text (on VText / VInput)
| Property | Values |
|---|---|
fontSize | number (dp) |
fontWeight | 'normal' | 'bold' | '100'–'900' |
fontStyle | 'normal' | 'italic' |
color | color string |
textAlign | 'left' | 'center' | 'right' |
lineHeight | number (dp) |
letterSpacing | number |
textDecorationLine | 'underline' | 'line-through' | 'none' |
textTransform | 'none' | 'uppercase' | 'lowercase' | 'capitalize' |
Shadow (iOS)
| Property | Values |
|---|---|
shadowColor | color string |
shadowOffset | { width: number, height: number } |
shadowOpacity | 0–1 |
shadowRadius | number |
Elevation (Android)
| Property | Values |
|---|---|
elevation | number (higher = more shadow) |
elevation is Android-only and is a no-op on iOS/macOS. It is required for shadows to render on Android — the iOS shadow* properties have no effect there, so set elevation alongside them for cross-platform cards:
const styles = createStyleSheet({
card: {
backgroundColor: '#FFFFFF',
borderRadius: 12,
padding: 16,
// iOS / macOS
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 8,
// Android
elevation: 3,
},
})
Transforms
The transform style accepts an array of transform objects. Transforms are applied in array order and do not affect layout (the view's measured box stays the same).
{
transform: [
{ translateX: 10 },
{ rotate: '45deg' },
{ scale: 1.2 },
],
}
Supported transform values
| Key | Type | Description |
|---|---|---|
translateX, translateY | number | Translation in points. |
scale, scaleX, scaleY | number | Scale factor (1 = unchanged). |
rotate, rotateX, rotateY, rotateZ | string | Rotation angle, e.g. '45deg' or '1.5rad'. rotateX/rotateY/rotateZ are 3D rotations around each axis. |
perspective | number | Perspective depth for 3D transforms (sets the m34 term). Larger values look flatter; smaller values exaggerate depth. |
skewX, skewY | string | Skew angle, e.g. '45deg'. |
3D transforms
Combine perspective with rotateX / rotateY for flip and tilt effects:
const styles = createStyleSheet({
card: {
transform: [
{ perspective: 800 },
{ rotateY: '25deg' },
],
},
})
Platform difference: skew
skewX and skewY are supported on iOS and macOS only. On Android the native View has no skew transform, so the request is logged and ignored (it will not crash). Keep skew out of cross-platform styles or guard it with selectPlatform.
Hairline borders
Use the exported hairlineWidth constant for the thinnest border the platform can render — useful for 1px-look dividers and separators:
import { createStyleSheet, hairlineWidth } from '@thelacanians/vue-native-runtime'
const styles = createStyleSheet({
separator: {
height: hairlineWidth,
backgroundColor: '#C6C6C8',
},
bordered: {
borderWidth: hairlineWidth,
borderColor: '#E5E5E5',
},
})
hairlineWidth is 0.5 and mirrors React Native's StyleSheet.hairlineWidth.
Common Layout Patterns
Center content
const styles = createStyleSheet({
centered: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
})
Equal-width grid (2 columns)
const styles = createStyleSheet({
grid: {
flexDirection: 'row',
flexWrap: 'wrap',
gap: 12,
},
gridItem: {
width: '48%', // Slightly less than 50% to account for gap
},
})
Sticky header + scrollable content
const styles = createStyleSheet({
screen: { flex: 1 },
header: {
padding: 16,
backgroundColor: '#FFFFFF',
borderBottomWidth: 1,
borderColor: '#E5E5E5',
},
content: { flex: 1 }, // Applied to VScrollView
})
<VView :style="styles.screen">
<VView :style="styles.header">
<VText>Header</VText>
</VView>
<VScrollView :style="styles.content">
<!-- Scrollable content here -->
</VScrollView>
</VView>
Card with shadow
const styles = createStyleSheet({
card: {
backgroundColor: '#FFFFFF',
borderRadius: 12,
padding: 16,
marginHorizontal: 16,
marginVertical: 8,
// iOS shadow
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 8,
// Android shadow
elevation: 3,
},
})
Row with spacer (left text, right button)
const styles = createStyleSheet({
row: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
padding: 16,
},
})
Theming & Dark Mode
Vue Native ships a built-in theme system: createTheme defines light/dark design tokens, <ThemeProvider> provides them via Vue's provide/inject, and createDynamicStyleSheet builds stylesheets that re-evaluate reactively when the active theme changes. Pair it with useColorScheme to follow the system dark-mode setting.
import { createTheme, createDynamicStyleSheet } from '@thelacanians/vue-native-runtime'
export const { ThemeProvider, useTheme } = createTheme({
light: { colors: { background: '#FFFFFF', text: '#1A1A1A' }, spacing: { md: 16 } },
dark: { colors: { background: '#000000', text: '#F5F5F5' }, spacing: { md: 16 } },
})
For the full walkthrough — token design, system sync, and persisting the user's preference — see the Theming guide.
Platform differences
| Property | iOS | Android |
|---|---|---|
shadowColor/Offset/Opacity/Radius | Native CALayer shadow | No effect (use elevation) |
elevation | No effect | Native View.elevation |
fontWeight | Full range '100'–'900' | Only 'normal' and 'bold' on some devices |
letterSpacing | Points | Treated as em on some Android versions |