Core concepts

Styling with sx

Every kara component is styled through props, and every value is a token: p="s16", bg="surface.primary", borderRadius="large". One sx surface resolves to SwiftUI modifiers on iOS, Compose modifiers on Android, or a React Native style — so call sites never branch on the platform. A raw number or hex where a token belongs does not type-check.

One surface, three targets

tsx
<VStack bg="fill.accent.base" borderRadius="large" p="s16" wide>   {/* SwiftUI / Compose modifiers */}
<Box bg="fill.accent.base" borderRadius="large" p="s16">             {/* a React Native style */}

The two lines look alike and resolve differently: the native stack becomes a background, a clip and padding modifiers; the Box becomes a View style. Both read the same tokens from the same theme.

There are two surfaces, and they are not interchangeable. Native components take the native sx — only what SwiftUI and Compose both honour. Box and the React Native tier take the wider view sx, with flexbox, gap, margins and per-corner radii. gap on a VStack does not compile; it wants spacing.

Native sx

What every native component takes, with the same meaning on both platforms.

PropTypeDescription
bgColorToken

The background colour, a palette token.

blurnumber

A Gaussian blur over the whole box, its radius in points.

borderColorColorToken

The stroke colour, with borderWidth.

borderRadiusRadiiSize

Rounds the corners — and clips the content, the background and the stroke to them.

borderWidthnumber

A stroke inside the edge, in points.

hnumber

A fixed height, in points.

minHnumber

The least height, in points.

minWnumber

The least width, in points.

offsetXnumber

Moves the box sideways after layout, in points — its neighbours stay where they were.

offsetYnumber

Moves the box up or down after layout, in points — its neighbours stay where they were.

opacityOpacitySize

How opaque the whole box is, an opacity token.

pSpaceSize

Padding on every side. A side or an axis of its own wins over it.

pbSpaceSize

Bottom padding.

plSpaceSize

Left padding — the leading edge, on the native surface.

prSpaceSize

Right padding — the trailing edge, on the native surface.

ptSpaceSize

Top padding.

pxSpaceSize

Left and right padding.

pySpaceSize

Top and bottom padding.

rotatenumber

Clockwise, in degrees.

scalenumber | {x?: number; y?: number}

Uniform, or per axis — an omitted axis stays at 1.

shadowShadowName

One of the theme's shadows — a drop, an inset highlight and a hairline ring, as the token defines.

tallboolean

Takes all the height the parent offers.

wnumber

A fixed width, in points.

wideboolean

Takes all the width the parent offers.

zIndexZIndexSize

Draws the box above or below its siblings, a z-index token.

bg
ColorToken

The background colour, a palette token.

blur
number

A Gaussian blur over the whole box, its radius in points.

borderColor
ColorToken

The stroke colour, with borderWidth.

borderRadius
RadiiSize

Rounds the corners — and clips the content, the background and the stroke to them.

borderWidth
number

A stroke inside the edge, in points.

h
number

A fixed height, in points.

minH
number

The least height, in points.

minW
number

The least width, in points.

offsetX
number

Moves the box sideways after layout, in points — its neighbours stay where they were.

offsetY
number

Moves the box up or down after layout, in points — its neighbours stay where they were.

opacity
OpacitySize

How opaque the whole box is, an opacity token.

p
SpaceSize

Padding on every side. A side or an axis of its own wins over it.

pb
SpaceSize

Bottom padding.

pl
SpaceSize

Left padding — the leading edge, on the native surface.

pr
SpaceSize

Right padding — the trailing edge, on the native surface.

pt
SpaceSize

Top padding.

px
SpaceSize

Left and right padding.

py
SpaceSize

Top and bottom padding.

rotate
number

Clockwise, in degrees.

scale
number | {x?: number; y?: number}

Uniform, or per axis — an omitted axis stays at 1.

shadow
ShadowName

One of the theme's shadows — a drop, an inset highlight and a hairline ring, as the token defines.

tall
boolean

Takes all the height the parent offers.

w
number

A fixed width, in points.

wide
boolean

Takes all the width the parent offers.

zIndex
ZIndexSize

Draws the box above or below its siblings, a z-index token.

Platform buckets

What only one platform can express goes in its own bucket: ios for SwiftUI modifiers with no honest Compose counterpart, android for the reverse. A bucket is inert on the other platform rather than a type error, so a call site never has to branch.

tsx
<HStack
  borderRadius="large"
  ios={{glass: 'regular'}}
  android={{weight: 1}}
  p="s16"
/>

ios

PropTypeDescription
aspectRationumber

Width over height, fitted within the proposed size. The escape hatch covers fill.

brightnessnumber

Brightens (positive) or darkens (negative) the box, from -1 to 1.

clippedboolean

Clips the content to the box's frame.

colorInvertboolean

Inverts every colour in the box.

containerRelativeFrame{alignment?: FrameAlignment; axes: 'both' | 'horizontal' | 'vertical'; count?: number; span?: number; spacing?: number}

Sizes the box relative to the nearest container: split it into count columns spacing apart and span span of them — SwiftUI's stand-in for a fractional width, which its frame lacks.

contentTransition{countsDown?: boolean; type: 'identity' | 'interpolate' | 'numericText' | 'opacity'}

How a content change animates when one is driven natively. countsDown is numericText's.

contrastnumber

Scales the contrast: 1 leaves it, 0 is flat grey, above 1 sharpens it.

controlSize'extraLarge' | 'large' | 'mini' | 'regular' | 'small'

Cascades to every control in the subtree, the SwiftUI environment way.

fixedSize'both' | 'horizontal' | 'vertical'

Holds the box at its ideal size on these axes, whatever its parent proposes.

glassGlassVariant | {cornerRadius?: number; interactive?: boolean; shape?: 'capsule' | 'circle' | 'containerRelativeShape' | 'ellipse' | 'rectangle' | 'roundedRectangle'; tint?: ColorToken; variant: GlassVariant}

Liquid Glass: a bare variant, or the full config. tint takes a theme token, and without a shape the glass traces the box's own borderRadius, the way strokes and shadows do.

grayscalenumber

Drains the colour, from 0 (none) to 1 (fully grey).

hiddenboolean

Invisible, but still taking up its layout space — unlike not rendering the box at all.

hueRotationnumber

In degrees.

idealHnumber

The height SwiftUI prefers when nothing constrains it — what fixedSize resolves to.

idealWnumber

The width SwiftUI prefers when nothing constrains it — what fixedSize resolves to.

ignoreSafeAreaboolean | {edges?: 'all' | 'bottom' | 'horizontal' | 'leading' | 'top' | 'trailing' | 'vertical'; regions?: 'all' | 'container' | 'keyboard'}

true extends into every safe area; the config narrows which regions and edges.

layoutPrioritynumber

Who gets space first when the parent runs short — not a proportional flex weight.

luminanceToAlphaboolean

Turns brightness into transparency: dark becomes clear, light stays opaque.

modifiersIosModifier[]

Escape hatch, appended last. Pass a stable array — a fresh one every render defeats the memo.

privacySensitiveboolean

Redacts only when an ancestor applies the privacy reason — inert on its own.

redacted'invalidated' | 'placeholder' | 'privacy'

Redacts this subtree — placeholder is SwiftUI's built-in skeleton. One reason here; combining several is escape-hatch territory.

saturationnumber

Scales the saturation: 1 leaves it, 0 is grey, above 1 intensifies it.

tintColorToken

Cascades to the controls and icons in the subtree.

unredactedboolean

Exempts this subtree from an ancestor's redacted.

aspectRatio
number

Width over height, fitted within the proposed size. The escape hatch covers fill.

brightness
number

Brightens (positive) or darkens (negative) the box, from -1 to 1.

clipped
boolean

Clips the content to the box's frame.

colorInvert
boolean

Inverts every colour in the box.

containerRelativeFrame
{alignment?: FrameAlignment; axes: 'both' | 'horizontal' | 'vertical'; count?: number; span?: number; spacing?: number}

Sizes the box relative to the nearest container: split it into count columns spacing apart and span span of them — SwiftUI's stand-in for a fractional width, which its frame lacks.

contentTransition
{countsDown?: boolean; type: 'identity' | 'interpolate' | 'numericText' | 'opacity'}

How a content change animates when one is driven natively. countsDown is numericText's.

contrast
number

Scales the contrast: 1 leaves it, 0 is flat grey, above 1 sharpens it.

controlSize
'extraLarge' | 'large' | 'mini' | 'regular' | 'small'

Cascades to every control in the subtree, the SwiftUI environment way.

fixedSize
'both' | 'horizontal' | 'vertical'

Holds the box at its ideal size on these axes, whatever its parent proposes.

glass
GlassVariant | {cornerRadius?: number; interactive?: boolean; shape?: 'capsule' | 'circle' | 'containerRelativeShape' | 'ellipse' | 'rectangle' | 'roundedRectangle'; tint?: ColorToken; variant: GlassVariant}

Liquid Glass: a bare variant, or the full config. tint takes a theme token, and without a shape the glass traces the box's own borderRadius, the way strokes and shadows do.

grayscale
number

Drains the colour, from 0 (none) to 1 (fully grey).

hidden
boolean

Invisible, but still taking up its layout space — unlike not rendering the box at all.

hueRotation
number

In degrees.

idealH
number

The height SwiftUI prefers when nothing constrains it — what fixedSize resolves to.

idealW
number

The width SwiftUI prefers when nothing constrains it — what fixedSize resolves to.

ignoreSafeArea
boolean | {edges?: 'all' | 'bottom' | 'horizontal' | 'leading' | 'top' | 'trailing' | 'vertical'; regions?: 'all' | 'container' | 'keyboard'}

true extends into every safe area; the config narrows which regions and edges.

layoutPriority
number

Who gets space first when the parent runs short — not a proportional flex weight.

luminanceToAlpha
boolean

Turns brightness into transparency: dark becomes clear, light stays opaque.

modifiers
IosModifier[]

Escape hatch, appended last. Pass a stable array — a fresh one every render defeats the memo.

privacySensitive
boolean

Redacts only when an ancestor applies the privacy reason — inert on its own.

redacted
'invalidated' | 'placeholder' | 'privacy'

Redacts this subtree — placeholder is SwiftUI's built-in skeleton. One reason here; combining several is escape-hatch territory.

saturation
number

Scales the saturation: 1 leaves it, 0 is grey, above 1 intensifies it.

tint
ColorToken

Cascades to the controls and icons in the subtree.

unredacted
boolean

Exempts this subtree from an ancestor's redacted.

android

PropTypeDescription
align'bottom' | 'bottomCenter' | 'bottomEnd' | 'bottomStart' | 'center' | 'centerEnd' | 'centerHorizontally' | 'centerStart' | 'centerVertically' | 'end' | 'start' | 'top' | 'topCenter' | 'topEnd' | 'topStart'

This box's own placement inside a ZStack parent — Compose's per-child Modifier.align.

animateContentSizeboolean

fillMaxHeightnumber

Fraction of the parent's height (0–1). The whole of it is tall, which works on iOS too.

fillMaxWidthnumber

Fraction of the parent's width (0–1). The whole of it is wide, which works on iOS too.

imePaddingboolean

matchParentSizeboolean

modifiersAndroidModifier[]

Escape hatch, appended last. Pass a stable array — a fresh one every render defeats the memo.

weightnumber

Proportional share of the leftover space in a Row or Column, the way flexGrow behaves.

wrapContentHeight'bottom' | 'centerVertically' | 'top' | boolean

wrapContentWidth'centerHorizontally' | 'end' | 'start' | boolean

align
'bottom' | 'bottomCenter' | 'bottomEnd' | 'bottomStart' | 'center' | 'centerEnd' | 'centerHorizontally' | 'centerStart' | 'centerVertically' | 'end' | 'start' | 'top' | 'topCenter' | 'topEnd' | 'topStart'

This box's own placement inside a ZStack parent — Compose's per-child Modifier.align.

animateContentSize
boolean
fillMaxHeight
number

Fraction of the parent's height (0–1). The whole of it is tall, which works on iOS too.

fillMaxWidth
number

Fraction of the parent's width (0–1). The whole of it is wide, which works on iOS too.

imePadding
boolean
matchParentSize
boolean
modifiers
AndroidModifier[]

Escape hatch, appended last. Pass a stable array — a fresh one every render defeats the memo.

weight
number

Proportional share of the leftover space in a Row or Column, the way flexGrow behaves.

wrapContentHeight
'bottom' | 'centerVertically' | 'top' | boolean
wrapContentWidth
'centerHorizontally' | 'end' | 'start' | boolean

React Native sx

The React Native tier — Box, Pressable, GlassView, the Rn* views — takes everything above except the buckets, plus the layout only a React Native style has. Lengths there may also be percentages: w="50%".

Flex

PropTypeDescription
alignViewStyle['alignItems']

alignContentViewStyle['alignContent']

alignSelfViewStyle['alignSelf']

basisSxDimension

columnGapSpaceSize

flexnumber

flexDirectionViewStyle['flexDirection']

gapSpaceSize

grownumber

justifyViewStyle['justifyContent']

rowGapSpaceSize

shrinknumber

wrapViewStyle['flexWrap']

align
ViewStyle['alignItems']
alignContent
ViewStyle['alignContent']
alignSelf
ViewStyle['alignSelf']
basis
SxDimension
columnGap
SpaceSize
flex
number
flexDirection
ViewStyle['flexDirection']
grow
number
justify
ViewStyle['justifyContent']
rowGap
SpaceSize
shrink
number
wrap
ViewStyle['flexWrap']

Margins

Position and overflow

PropTypeDescription
bottomSxDimension

displayViewStyle['display']

leftSxDimension

overflowViewStyle['overflow']

positionViewStyle['position']

rightSxDimension

topSxDimension

bottom
SxDimension
display
ViewStyle['display']
left
SxDimension
overflow
ViewStyle['overflow']
position
ViewStyle['position']
right
SxDimension
top
SxDimension

Sizing

PropTypeDescription
aspectRationumber

maxHSxDimension

maxWSxDimension

aspectRatio
number
maxH
SxDimension
maxW
SxDimension

Borders and corners

PropTypeDescription
borderBottomWidthnumber

borderLeftWidthnumber

borderRightWidthnumber

borderStyleViewStyle['borderStyle']

borderTopWidthnumber

borderBottomWidth
number
borderLeftWidth
number
borderRightWidth
number
borderStyle
ViewStyle['borderStyle']
borderTopWidth
number
PropTypeDescription
borderBottomLeftRadiusRadiiSize

borderBottomRadiusRadiiSize

borderBottomRightRadiusRadiiSize

borderLeftRadiusRadiiSize

borderRightRadiusRadiiSize

borderTopLeftRadiusRadiiSize

borderTopRadiusRadiiSize

borderTopRightRadiusRadiiSize

borderBottomLeftRadius
RadiiSize
borderBottomRadius
RadiiSize
borderBottomRightRadius
RadiiSize
borderLeftRadius
RadiiSize
borderRightRadius
RadiiSize
borderTopLeftRadius
RadiiSize
borderTopRadius
RadiiSize
borderTopRightRadius
RadiiSize

Responsive values

Layout keys take a value per breakpoint as well as a single one — mobile first, from base up. Colours, radii, shadows and the platform buckets stay scalar.

tsx
<Box flexDirection={{base: 'column', md: 'row'}} gap="s16" p={{base: 's16', md: 's32'}} />
BreakpointFrom
base0
sm576
md768
lg992
xl1200

Dimmed colours

A colour token takes an opacity token after a colon: bg="fill.accent.base:o50". It multiplies whatever the colour already is. Tokens iOS resolves by name (UIKit semantic colours) have no alpha to multiply, so only the ones kara owns the value of can be dimmed — the types say which.

Your own components

sx(Component) gives any component that takes style the same props, memoised. useSx and useSplitSx are the hooks underneath, for a component that owns its style prop.

tsx
import {sx} from '@hzblj/kara-ui'
import {Image as ExpoImage} from 'expo-image'

/** Any component that takes `style` — wrapped once, at module scope. */
export const Photo = sx(ExpoImage)

<Photo borderRadius="large" h={220} source={photo} w="100%" />