CSS on Lynx
Lynx draws your app with its own CSS engine, not a browser's. Most layout and visual CSS carries over, but some defaults differ and some features are missing. This page covers what trips up web developers, and what to write instead.
For every property and value Lynx accepts, see the Lynx CSS reference.
At a glance
The root is page
pageis the root element of every Lynx app. Its selector does the job of:rootandbody.htmlandbodymatch nothing.- Put app-wide CSS variables on
page. Every element below it can read them.
Layout
- Elements stack vertically by default. Lynx's default
displayislinear, which lays children out in a column.flexandgridwork as on the web.block,inlineandinline-blockdon't exist. flex-direction: rowneedsdisplay: flex. On a linear element it does nothing. In Tailwind, writeflex flex-row, notflex-rowalone.- Children stretch across their parent. A child with no width fills the parent's cross axis. Add
align-self: flex-start(Tailwindself-start) to make a badge hug its content. - Boxes are
border-box, and margins never collapse: a 16px bottom margin above a 16px top margin makes 32px. min-widthandmin-heightdefault to 0, notauto. A flex item can shrink below its content and clip its text. Set amin-widthto stop it.- Every element is
position: relativeby default, soz-indexworks on any element. - Siblings paint in source order. A later sibling draws on top, even when an earlier one is
position: absolute. position: fixedacts likeposition: absoluteagainst thepage. For floating UI such as menus, use<overlay>.position: stickyworks only inside a<scroll-view>.overflowisvisibleorhidden. A<view>never scrolls. Use<scroll-view>or<list>.
Elements and layout covers which element to use for what.
Text
- Text renders only inside
<text>. Text placed directly in a<view>doesn't show. - Put text styles on the
<text>. Acolor,font-sizeorline-heighton a<view>doesn't reach the text inside it (see Inheritance). line-heightworks only on<text>.white-spaceisnormalornowrap. There's nopre, so spaces and line breaks always collapse.- There's no
text-transform, so Tailwind'suppercasedoes nothing. Change the case in TypeScript. - There's no
text-align: justify. - Clamp lines with the
text-maxlineattribute, as above, instead of-webkit-line-clamp. Pair it withtext-overflow: ellipsis: without it, Android draws the extra lines past the box.
Inheritance
- CSS properties don't inherit by default. Each element gets only the rules that match it. Inheritance has a performance cost, so Lynx leaves it off.
- A nested
<text>is the exception. An inner<text>takes its parent<text>'scolorandfont-family. - CSS variables always inherit, as on the web. Set them on a parent and use
var()anywhere below it. - There's no
inherit,initialorunsetkeyword.
To make text properties inherit as on the web, turn on enableCSSInheritance in a lynx.config.ts at the project root:
Then color, direction, font-family, font-size, font-style, font-weight, letter-spacing, line-height, text-align, text-decoration and text-shadow inherit. To pick the list yourself, also pass customCSSInheritanceList. Both are in the plugin options on the @blotch/angular-lynx-build page.
Units
remis 14px, not 16px. Rem-based sizes come out 12.5% smaller than in a browser. That includes Tailwind's scale:h-11is 38.5px. Usepxwhere a size must line up with something exact.- Lengths need units. Only
0can go without one, andmargin: 5is ignored. Times need units too:0ms, not0. calc()andenv()work.min(),max()andclamp()don't.
Colors
- Space-separated
hsl()fails silently. The element draws transparent, with no warning. Lynx's parser readshsl()only in its comma form. currentColorisn't supported. Use the color itself, or a CSS variable.
Theme colors in CSS variables
Store each color whole in a variable, then reference the variable directly:
- Don't store raw channels (
--primary: 240 5.9% 10%) to wrap later inhsl(var(--primary)). That expands to the space-separatedhsl()Lynx drops, which is why shadcn-style themes render blank. - For a translucent shade, add a variable of its own, like
--primary-subtle. - Set variables in a stylesheet or a static
styleattribute. A[style.--name]binding doesn't reach the element yet.
The UI components theme works this way. See Theming.
Selectors and rules
These work: type (text), class, ID, *, compound (.a.b), descendant, child (>), sibling (+, ~), :not() and :active (while a finger is down). The at-rules are @font-face, @keyframes and @import.
Descendant selectors don't re-match when a class changes. By default, .selected .label applies only when classes are first set. Toggle a class on the element itself, or switch CSS variables, instead. The enableCSSInvalidation plugin option makes them re-match, at some cost in speed.
Component styles
styles and styleUrl work, but component styles aren't isolated on Lynx:
ViewEncapsulation.Emulated(the default) keeps your selectors as written. Lynx elements carry no per-component attributes, so a.titlerule in one component also matches.titlein another. Prefix class names with the component name, as above, or use CSS Modules for names that can't clash.:hostand::ng-deepdon't work. To style the host element, give it a class throughhost: { class: '…' }.ViewEncapsulation.Nonestyles the same way asEmulated.ViewEncapsulation.ShadowDomfalls back toNone, with a warning at build time and run time. Lynx has no shadow DOM.
