Reference
Styles and layout
The flexbox subset the Rust core resolves, the values it accepts, and what a style name it does not know does.
Layout is not the platform’s. Flexbox runs once, in the core, over taffy, and every host is handed absolute frames in logical points. So the set of styles below is the same on a phone, a TV and a watch — a template that lays out one way lays out that way everywhere.
There is no cascade, no inheritance, no selectors and no stylesheets. A style is written on the node it applies to:
<an-view [style.flexDirection]="'row'" [style.gap]="'12'" [style.padding]="'16'"> <an-view [style.flex]="'1'"><an-text>left</an-text></an-view> <an-view [style.width]="'80'"><an-text>right</an-text></an-view></an-view>Values are strings because that is what Angular’s [style.x] binding produces.
The core parses them once, on assignment.
What the core accepts
Section titled “What the core accepts”51 names, and nothing else. They are declared in
crates/an-layout/src/style.rs and mirrored on the TypeScript side in
packages/platform-native/src/style-names.ts; scripts/check-styles.sh fails
if the two lists drift apart.
Box and flow
Section titled “Box and flow”| Style | Values | Default |
|---|---|---|
display |
flex, none |
flex |
position |
relative, absolute (static reads as relative, fixed as absolute) |
relative |
overflow |
visible, hidden, scroll |
visible |
boxSizing |
border-box, content-box |
border-box |
overflow sets both axes at once — there is no overflowX.
What clipping actually does
Section titled “What clipping actually does”hidden and scroll both clip: the node’s box is the whole of what it may
paint, and a child that sticks out is cut off at the edge rather than drawn over
whatever is next to it. visible, the default, lets it through.
Two nodes clip whatever the template says:
- The root. It is the app’s body. Nothing an app contains may be painted outside the window — a body that does not clip is a canvas, and content pushed past its edge goes on existing off-screen.
- Anything scrollable. Otherwise its content is drawn over the things around it as soon as there is more of it than fits.
Nothing scrolls sideways
Section titled “Nothing scrolls sideways”contentSize is computed assuming the overflow goes downwards, so a scrollable
node’s content is clamped to its own width — in the core, and again in the
host where the scrolling happens.
That clamp is not tidiness. A UIScrollView scrolls on whichever axis its
content is bigger, so content a few points too wide — one image reporting its
intrinsic size into a row, say — turns the whole page into something that can be
dragged sideways until there is nothing on screen. Vertically, overflow is the
point; horizontally it is always a mistake somewhere else, and it should show up
as a clipped edge rather than as a screen that can be swiped away.
scripts/check-clip.sh asserts both: that the root and every scrollable node
clip, that an ordinary container does not, and that no scrollable node is wider
inside than out.
| Style | Values | Default |
|---|---|---|
flexDirection |
row, column, row-reverse, column-reverse |
column |
flexWrap |
nowrap, wrap, wrap-reverse |
nowrap |
justifyContent |
flex-start, flex-end, center, space-between, space-around, space-evenly, stretch |
unset |
alignItems |
flex-start, flex-end, center, stretch, baseline |
unset |
alignSelf |
same as alignItems |
unset |
alignContent |
same as justifyContent |
unset |
flex |
a number | — |
flexGrow |
a number | 0 |
flexShrink |
a number | 0 |
flexBasis |
points, percentage or auto |
auto |
Two defaults are React Native’s rather than CSS’s, and both matter: children stack downwards unless told otherwise, and nothing shrinks below its size unless asked to.
flex: N is the shorthand, and it means what it means in CSS: grow N, shrink
1, basis 0. It is what almost everybody writes instead of the three
separately.
justifyContent and alignContent accept the same keyword set; alignItems
and alignSelf accept baseline but not the space-* family.
| Style | Values |
|---|---|
width, height |
points, percentage, auto |
minWidth, minHeight, maxWidth, maxHeight |
points, percentage, auto |
aspectRatio |
a number — width divided by height |
An unset maximum is auto, not zero. A maxWidth of zero would leave the view
with no size at all, which is the opposite of “there is no maximum”.
Spacing
Section titled “Spacing”| Style | Values |
|---|---|
margin, marginTop, marginRight, marginBottom, marginLeft |
points, percentage, auto |
marginHorizontal, marginVertical |
the same, two edges at a time |
padding, paddingTop, paddingRight, paddingBottom, paddingLeft |
points, percentage |
paddingHorizontal, paddingVertical |
the same, two edges at a time |
gap, rowGap, columnGap |
points, percentage |
marginStart and marginEnd are accepted as spellings of marginLeft and
marginRight, and the same for padding. They are not direction-aware: this
is not a right-to-left layout engine, and the names are taken only so that a
template written with them does not silently do nothing.
Margins take auto; padding and gap do not.
Border width and offsets
Section titled “Border width and offsets”| Style | Values |
|---|---|
borderWidth, borderTopWidth, borderRightWidth, borderBottomWidth, borderLeftWidth |
points, percentage |
top, right, bottom, left |
points, percentage, auto |
Border width is layout, so it lives here. Border colour and radius are not
— they are props on the primitive, along with backgroundColor. See
Components.
Values
Section titled “Values”| Written | Read as |
|---|---|
'16', '16px' |
16 logical points |
'50%' |
half of the parent’s corresponding dimension |
'auto' |
the prop’s automatic behaviour |
'row', 'center', … |
a keyword, where the prop takes one |
'' |
unset — back to the default |
Units are logical points, not physical pixels. The device’s scale factor never enters a template; it is the platform’s business when it draws.
Both spellings of every hyphenated keyword work: space-between and
spaceBetween, flex-start and flexStart (and start), column-reverse and
columnReverse. Angular hyphenates style names before handing them over, so
[style.flexDirection] and [style.flex-direction] are the same thing to the
core.
A value the core cannot parse becomes Unset — the prop falls back to its
default rather than to whatever it held before.
Font styles are not styles
Section titled “Font styles are not styles”Nine names look like CSS but are props, not layout:
fontSize · fontWeight · fontStyle · fontFamily · lineHeight ·
letterSpacing · color · textAlign · numberOfLines
The core needs them to measure text and the host needs them to draw it, so
they travel as props on the node. Writing them as [style.fontSize] works —
the renderer recognises the nine and reroutes them — but the typed input
[fontSize] is what the template compiler can check, and it is what to write.
Before that reroute existed, [style.fontSize] did nothing at all: the text was
measured in the default font and drawn in UIKit’s, so it spilled out of a box
that had been sized for something smaller and the parent clipped it. It looked
like text disappearing.
An unknown style says so
Section titled “An unknown style says so”Angular accepts [style.whatever] without a murmur. So the renderer checks the
name against the list above and warns, once per name, when nobody is going
to look at it — naming the style, saying it will do nothing, and pointing at
where it probably belonged: a property of the control, such as a colour, a
title or a value, is a typed input and not a style.
Once per name and not once per write: the same style is set again on every change detection pass, and warning every time would fill the log without saying anything new.
This is the shape the whole framework takes on a gap. A style that travels, that no host reads and that raises nothing is the most expensive kind of bug there is: it looks like the feature simply does not work, and there is nothing to grep for.
Classes and !important
Section titled “Classes and !important”addClass accumulates names and sends them as a className prop. Nothing in
the core or the hosts resolves them — there are no stylesheets — so a class is
inert unless a styling layer above does something with it.
RendererStyleFlags2.Important is ignored and the value applied. Without a
cascade there is nothing for !important to win against.
Measuring: where a size comes from when you do not give one
Section titled “Measuring: where a size comes from when you do not give one”A node with no explicit size is measured by asking the platform, through the
TextMeasurer trait. There are three kinds of leaf:
- Text — measured in the system’s real typeface, with the font props above, against the available width. The intrinsic minimum gets a separate question: asking for it as “available width zero” looks equivalent and is not — both platforms answer zero, and then the text shrinks to nothing as soon as nobody imposes a width.
- Controls — a
UISwitch, aMaterialButton. Each is measured once at startup from a sample control, because creating one needs the main thread and the measurer lives on the engine’s. - Images — from the intrinsic size, keeping the aspect ratio when only one dimension is fixed.
A node with a measure function is a leaf: layout does not descend below it, children or no children.
That startup measurement is worth knowing about when a control is asked to hold
more than the sample did. A button with an [ios].subtitle is two lines tall
and the sample was one, so it needs an explicit height in the template or the
title gets clipped.
contentSize, and the axis that is missing
Section titled “contentSize, and the axis that is missing”For a scrollable node the core also reports how much room the children take,
which is what a UIScrollView wants as its contentSize.
It computes that assuming the overflow goes downwards. Horizontal scrolling
is therefore not a host prop that is missing: it is work in an-core.