CSS Properties
ReactUnity allows you to style elements using CSS. The CSS properties works the same as in browsers unless specified otherwise.
:root { justify-content: space-around; text-align: center; background-color: #a9b2ba; } view { align-self: center; width: 100px; height: 100px; background-color: coral; cursor: pointer; } view:hover { background-color: blue; }
CSS Properties
List of supported CSS properties in alphabetical order:
align-contentalign-itemsalign-selfanimation-delayanimation-directionanimation-durationanimation-fill-modeanimation-iteration-countanimation-nameanimation-play-stateanimation-range-endanimation-range-startanimation-timelineanimation-timing-functionappearanceaspect-ratioaudio-clipaudio-delayaudio-iteration-countaudio-pitchaudio-volumebackdrop-filterbackface-visibilitybackground-blend-modebackground-clipbackground-colorbackground-imagebackground-position-xbackground-position-ybackground-repeat-xbackground-repeat-ybackground-sizeblock-sizeborder-block-colorborder-block-end-colorborder-block-end-styleborder-block-end-widthborder-block-start-colorborder-block-start-styleborder-block-start-widthborder-bottom-colorborder-bottom-left-radiusborder-bottom-right-radiusborder-bottom-styleborder-bottom-widthborder-end-end-radiusborder-end-start-radiusborder-end-widthborder-image-outsetborder-image-repeatborder-image-sliceborder-image-sourceborder-image-widthborder-inline-colorborder-inline-end-colorborder-inline-end-styleborder-inline-end-widthborder-inline-start-colorborder-inline-start-styleborder-inline-start-widthborder-left-colorborder-left-styleborder-left-widthborder-right-colorborder-right-styleborder-right-widthborder-start-end-radiusborder-start-start-radiusborder-start-widthborder-top-colorborder-top-left-radiusborder-top-right-radiusborder-top-styleborder-top-widthbottombox-shadowbox-sizingcaret-colorclip-pathcolorcolor-schemecolumn-gapcontainer-namecontainer-typecontentcursordirectiondisplayendfillfilterflex-basisflex-directionflex-growflex-shrinkflex-wrapfont-familyfont-sizefont-stylefont-variantfont-variant-capsfont-weightheightimage-renderinginline-sizeinset-block-endinset-block-startinset-inline-endinset-inline-startisolationjustify-contentleftletter-spacingline-clampline-heightmargin-block-endmargin-block-startmargin-bottommargin-endmargin-horizontalmargin-inline-endmargin-inline-startmargin-leftmargin-rightmargin-startmargin-topmargin-verticalmask-imagemask-modemask-position-xmask-position-ymask-repeat-xmask-repeat-ymask-sizemask-typemax-block-sizemax-heightmax-inline-sizemax-linesmax-widthmin-block-sizemin-heightmin-inline-sizemin-widthmix-blend-modemotion-delaymotion-durationmotion-timing-functionnavigationobject-fitobject-positionopacityorderoutline-coloroutline-offsetoutline-styleoutline-widthoverflow-xoverflow-yoverscroll-behavior-blockoverscroll-behavior-inlineoverscroll-behavior-xoverscroll-behavior-ypadding-block-endpadding-block-startpadding-bottompadding-endpadding-horizontalpadding-inline-endpadding-inline-startpadding-leftpadding-rightpadding-startpadding-toppadding-verticalperspectiveperspective-originpointer-eventspositionrightrotaterow-gapscalescroll-behaviorscroll-margin-block-endscroll-margin-block-startscroll-margin-bottomscroll-margin-inline-endscroll-margin-inline-startscroll-margin-leftscroll-margin-rightscroll-margin-topscroll-padding-block-endscroll-padding-block-startscroll-padding-bottomscroll-padding-inline-endscroll-padding-inline-startscroll-padding-leftscroll-padding-rightscroll-padding-topscroll-snap-alignscroll-snap-stopscroll-snap-typescroll-timeline-axisscroll-timeline-namescrollbar-guttershape-renderingsorting-layerstartstate-durationstroketext-aligntext-decoration-colortext-decoration-linetext-overflowtext-shadowtext-stroke-colortext-stroke-widthtext-transformtext-wraptimeline-scopetoptransform-origintransition-delaytransition-durationtransition-play-statetransition-propertytransition-timing-functiontranslatetranslate-zvertical-alignview-timeline-axisview-timeline-insetview-timeline-namevisibilitywhite-spacewidthword-spacingz-index
CSS Shorthands
List of supported CSS shorthands in alphabetical order:
allanimationanimation-rangeaudiobackgroundbackground-positionbackground-repeatborderborder-block-colorborder-block-styleborder-block-widthborder-bottomborder-colorborder-imageborder-inline-colorborder-inline-styleborder-inline-widthborder-leftborder-radiusborder-rightborder-styleborder-topborder-widthcontainerflexflex-flowfontgapinsetinset-blockinset-inlinemarginmargin-blockmargin-inlinemaskmask-positionmask-repeatmotionoutlineoverflowoverscroll-behaviorpaddingpadding-blockpadding-inlineplace-contentplace-itemsplace-selfscroll-marginscroll-margin-blockscroll-margin-inlinescroll-paddingscroll-padding-blockscroll-padding-inlinescroll-timelinescrollbar-colorscrollbar-widthtext-decorationtext-stroketransformtransitionview-timeline
Math functions
calc(), min(), max() and clamp() are joined by round() with its nearest, up, down and to-zero strategies, mod(), rem(), abs(), sign(), sin(), cos(), tan(), asin(), acos(), atan(), atan2(), pow(), sqrt(), hypot(), log() and exp(). They nest freely inside one another and inside calc(), and take var() operands. The constants pi, e, infinity, -infinity and NaN are known.
view {
animation-duration: round(up, 1250ms, 1s);
opacity: calc(0.5 + 0.5 * sin(var(--phase)));
flex-grow: calc(pow(2, var(--level)) - 1);
}
A calculation on a layout value — width, left, flex-basis, translate and the rest — has to come out
as something Yoga can hold, which is a length or a percentage but never both. width: calc(1/2 * 100%) is
50%, so every fraction utility a CSS framework emits lays out; width: calc(100% - 2rem) has no unit to
resolve to and is dropped. The percentage survives a sub-expression, parenthesised or a nested calc(), so
calc(calc(1 / 2 * 100%) * -1) — the shape a negative fraction utility compiles to — is -50%. A min(),
max() or clamp() whose arguments are all percentages is a percentage too; one that mixes a percentage
with a length resolves the percentage against the box, which only the sizing properties and the border
widths have an answer for. infinity is the largest length the implementation supports rather than a real
infinity, which is what makes border-radius: calc(infinity * 1px) a pill.
place-content, place-items and place-self set the align and justify pair in one declaration. Yoga has no justify-items or justify-self, so the last two set their align property and only check that the second value is a keyword. The web spellings start, end, self-start, self-end, left, right and normal work on the align and justify longhands too.
Display and the flex defaults
Every element lays out with flexbox, and display picks the defaults a browser would give it. An
element with no display, or with display: block, stacks its children the way a block does, and
display: flex lays them out in a row. Either way flex-direction, gap, align-items and the rest
work on it. inline, inline-block and flow-root read as block, inline-flex as flex, and
none and contents do what they do on the web. There is no inline formatting for the inline
spellings to differ in, so each is the block value it reduces to rather than a dropped declaration.
flex-shrink defaults to 1 as on the web, so an item gives up space before it overflows, and the
flex shorthand fills in the web’s values: flex: 1 is 1 1 0%, flex: auto is 1 1 auto and
flex: none is 0 0 auto.
Logical properties
The inline axis is the one direction turns around, and the block axis is always vertical —
writing-mode does not exist here, so block-start is the top and block-end the bottom whatever
else is going on.
view {
padding-inline: 4px 12px;
padding-block-start: 8px;
}
direction is inherited, so setting it once turns the inline axis around for a whole subtree:
#panel {
direction: rtl;
}
Which physical side each inline value lands on is decided from the direction the element ends up with
— an element does not need a direction of its own to follow one set on an ancestor. For everything
Yoga lays out that decision is Yoga’s, made during layout; the painted border properties below are
resolved by ReactUnity itself, by the same rule. An inline value takes precedence over the physical
property covering the same edge, whichever order the two are declared in; CSS would let the later
declaration win, and it is the one place the two disagree, so do not set both on one edge.
The painted halves of a border have logical spellings too: border-inline-color,
border-block-color, border-inline-style, border-block-style, their four -start-/-end-
longhands each, and the four logical corners — border-start-start-radius, border-start-end-radius,
border-end-start-radius and border-end-end-radius, named block edge first. This is what Tailwind’s
border-s-*, border-x-<color> and rounded-s-* need.
inline-size, block-size and their min-/max- forms are plain aliases for width and height.
They are not edges: which axis is inline is writing-mode’s to decide rather than direction’s, so
with no writing modes here the inline axis stays horizontal however the direction runs.
Under UIToolkit the inline axis is always the physical one: UIElements has no start and end edge to
resolve against, and direction does nothing there. The painted border properties are the exception
— those are resolved before they reach UIElements, so they follow direction under both renderers.
Text decoration
text-decoration is a shorthand for the line and its colour. TextMeshPro draws the line from the
font asset’s own metrics and has only a solid form, so the style and thickness components are read
and then dropped — the point being that they do not take the line down with them:
a { text-decoration: underline red; }
a:hover { text-decoration: underline dotted 2px currentcolor; }
del { text-decoration: line-through #888; }
underline, line-through and none are the lines, and they combine. overline and blink are
not accepted at all, because nothing can draw them, so a declaration asking for one is dropped.
text-decoration-style and text-decoration-thickness do not exist as longhands for the same
reason — there is nothing for them to set.
text-decoration-color defaults to currentcolor and, unlike on the web, is inherited: the line
itself rides on font-style, which inherits, so a non-inherited colour would draw nested text’s
underline in the wrong one. A colour that differs from color is applied through TextMeshPro’s rich
text tags, which means it needs richText on — the default for a <text> — and is skipped when it
is off.
font-style is one property carrying italic, bold and the letter casing as well as the two lines, so
the shorthand sets only the components you name: text-decoration: red leaves the line as it is,
where CSS would reset it to none.
The root element
:root is the element ReactUnity renders into, the one everything else is under. :host names the
same element — there are no shadow trees here for the two to tell apart — so a stylesheet written
for the web works either way, and :root, :host { ... }, which is how Tailwind opens its theme
block, applies once.
:root, :host { --brand: #4f46e5; }
:host(.dark) { background-color: #0f172a; }
:host(...) matches the root only when the root also matches its argument, and the argument weighs
what it would on its own. The root is an ordinary element in this: give it a class from C# with
context.Host.ClassList.Add("dark") and the rules naming it restyle. :host-context() has nothing
to ask — it is about the host’s ancestors, and the root has none — so it is read as a custom state
like any other unknown pseudo-class.
Custom states
Any pseudo-class ReactUnity does not know is a custom state: view:busy matches while a state named
busy is started on the element, from C# through component.StateStyles.StartState("busy") and
EndState. :state(busy) is the standard spelling of the same thing, and the one to prefer, since a
bare unknown name is warned about once in case it was a typo. Both take the specificity of a
pseudo-class.
.slot:state(busy) {
opacity: 0.5;
}