Known limits
Everything below is a gap or approximation, kept in one place so nothing is discovered by surprise. This mirrors
LIMITS.md in the repository root — update that
file when a limit is fixed or a new one is found, and this page along with it.
Tags used:
- Planned: intended work.
- Approximation: implemented, but deliberately simpler than the spec.
- Unsupported: not implemented and not currently scheduled.
1. Verification and platform
| Limit | Notes |
|---|---|
| No automated tests | Unsupported. Behaviour is checked by rendering headless PNGs (--screenshot) and reading them. Two self-checks exist: --verify compares the incremental layout and restyle against a from-scratch one after scripted interactions, and --bench reports per-phase frame cost. Neither is a test suite. Selector matching, the flex/grid algorithms, TextEditState and the animation maths are pure logic and good candidates for unit tests. |
| Only tested on Windows | The font fallback assumes Segoe UI, Times New Roman and Consolas. Other platforms need a font mapping table. |
| Live window only lightly exercised | The window was launched, captured from the screen once to confirm it draws, and its idle CPU use measured (about 1.6% of one core). Real mouse, keyboard, clipboard, cursor and multi-monitor DPI behaviour through Silk.NET have not been exercised by hand. |
| OpenGL 3.3 core required | Skia renders through a GL context from the window. There is no software fallback for the live window (the headless path is raster). |
| No OS dark-mode detection | UiHost.ColorScheme must be set by the host. prefers-color-scheme is matched against it. |
| Single window | One window per process. |
2. Rendering and performance
Measured on a 12,000-element page (1,500 rows in a scroller), Release build. A hover, scroll step or text change costs about 2 to 4 ms (CPU raster); the idle window renders nothing.
| Limit | Notes |
|---|---|
| Cold start is slow on big trees | About 450 to 530 ms for the first frame of 12,000 elements (renderer, style, layout). Not optimised. |
| A layout-affecting change relays out its dirty chain | Clean subtrees are reused (translated, not recomputed), but everything from the changed element up to the root is laid out again, and flex or grid containers re-measure their items. There is no relayout boundary, so a change near the root of a huge tree costs more than a change in a leaf. |
| Subtrees with absolute or fixed descendants are never reused | Any element that contains an out-of-flow box is re-laid out whenever its parent is. The flag is sticky until the node is recreated. |
| Visual bounds are recomputed for the dirty chain only | Correct, but the pass is whole-tree in a worst case such as a viewport resize. |
| The display list is rebuilt for every rendered frame | It is culled to what is on screen (28,500 ops became 316 on the stress page), so the rebuild is cheap, but it is not retained between frames. |
| No layer caching or damage rectangles | Measured and deliberately not built. On a GPU surface the heaviest demo (blurs, shadows, filters, gradients) costs 3.2 ms per frame and the stress page 2 to 3.5 ms, far below a 16 ms budget. The headless CPU raster path is much slower for blurs and filters (about 35 ms for the Effects page), so a CPU-only host would benefit. |
| Any input forces one frame | Even a pointer move over nothing repaints once. Hover and scroll are cheap, but input is not coalesced. |
| Event-driven rendering needs state changes to flag themselves | Renders happen when style or layout is dirty, an animation is running, input arrived, the caret blinks, or the window asks. A host that mutates the document without going through the node API would not wake it. |
| Per-pass intrinsic caches are discarded every layout | Widths and natural heights are recomputed for dirty subtrees each pass. |
| Coarse locking | One document lock guards tree mutation, style, layout, paint and input. |
State-dependent restyling falls back to broad invalidation for :not(:hover) and :is(:hover) | Plain :hover, :active, :focus and :focus-visible are tracked per selector and restyle only the elements a rule can affect. |
| Text uses plain Skia metrics | Planned. No HarfBuzz shaping, so kerning, ligatures, complex scripts and bidi (Arabic, Hebrew) are approximate. Caret x positions are measured on string prefixes. |
3. CSS
See CSS support for the condensed, by-topic version of this section.
Parsing and cascade
- Own parser, not ExCSS. It preserves custom properties and
var()as raw text. It does not validate declarations. - Unsupported at-rules are skipped whole:
@import,@layer,@container,@property,@page,@namespace. (@media,@supports,@keyframesand@font-faceare supported.) Rules inside@layeror@containerare dropped, not applied.@supportsis treated as always true. - CSS nesting is not supported.
- Selectors: no pseudo-elements (
::before,::after,::placeholder,::selection), no:has(),:focus-within,:nth-of-type,:first-of-type,:nth-last-child,:target,:visited. A selector using one is dropped. If every selector in a rule is dropped, the rule is dropped. - Media queries:
min/max-width,min/max-height,prefers-color-scheme,screen,all,and, and comma lists only. Nonot,only, range syntax,orientation,resolution,hover. - Keywords:
inheritworks for a subset of properties.initial,unset,revertare not supported. - Values:
calc()works for absolute lengths and numbers only (no%).min(),max(),clamp()andenv()are not supported. - Colors: hex,
rgb()/rgba(),hsl()/hsla(),currentColor,transparentand about 50 named colors. Nocolor(),lab(),oklch(),color-mix()or the full CSS named-color list. - Scoped CSS (
.razor.css) is not supported. Use global stylesheets orstyleattributes. !importantworks at declaration level.
Box model and text
- No margin collapsing: adjacent vertical margins add.
- Inline elements have no box of their own: a
<span>cannot have a background, border or padding that wraps its text. They are hit-testable through their text. <br>does not break lines. It is parsed but has no layout behaviour.<hr>has no UA style.- Not implemented:
float,white-space(so nopre/nowrap),vertical-align(inline items are centred in the line),text-align: justify,text-overflow,overflow-wrap,letter-spacing,word-spacing,text-transform,text-indent,line-clamp,columns,aspect-ratio,object-fit. border-styleother thannonedraws solid (no dashed, dotted, double, groove or ridge).box-sizing, min/max sizes,%and viewport units work.%heights resolve only against a definite parent height.%padding and margin resolve against the containing block width.- Elements not laid out as designed:
table(tables, rows and cells have no table layout),ul/ol/li(no bullets or numbering),img,svg,canvas,video,iframe. Unknown tags default to inline.
Visual effects
Supported: box-shadow (outer and inset, multiple layers, spread, blur), text-shadow, linear-gradient,
radial-gradient and their repeating- forms (hard stops, to <corner> and at <position> included),
2D transform with transform-origin, filter (blur, brightness, contrast, grayscale, sepia, saturate, hue-rotate,
invert, opacity, drop-shadow), per-corner border-radius (px and %), and mixed-colour borders with mitred corners.
- Not implemented:
clip-path,backdrop-filter,mix-blend-mode,mask,border-image,conic-gradient,filter: url(),background-image: url()and any raster or SVG image, andbackground-position,background-size,background-repeat,background-clip,background-attachment(gradients always fill the border box). - Gradient details: colour stop hints (midpoints) and interpolation colour spaces are not supported. A
transparentstop borrows its neighbour's colour to avoid a grey fringe, which matches browsers' premultiplied result. - Radii: the elliptical
a / bsyntax is not supported. A percentage radius uses the smaller side of the box, so50%gives a circle or a pill but not an ellipse on a non-square box (Approximation). - Transforms are 2D only: no
perspective,rotateX/Y/Z,translate3ddepth, or the standalonetranslate,scale,rotateproperties. A transform paints and hit-tests correctly, but it does not change layout, scrollable overflow,scrollIntoView, or the position of a select dropdown (Approximation). - Box shadows and culling: content more than 160 px outside the visible area is skipped, so a shadow or outline reaching further than that into view could be clipped.
- The select dropdown's own shadow is hard-coded.
Motion
Supported: transition (all longhands, all, shorthands such as margin, cubic-bezier, steps, delays, retargeting while in flight)
and @keyframes animation (animation shorthand and longhands, iteration count including infinite, direction, fill mode,
play state, multiple animations). Animatable: opacity, colours, lengths and sizes, margins, paddings, borders, radii, transform,
box-shadow, text-shadow, filter, flex properties, gaps, font-size, outline.
- No animation events:
transitionend,animationstart,animationendandanimationiterationare not dispatched to Blazor handlers. - Interpolation limits: lengths of different units (px vs %) and
autoswitch at the halfway point rather than blending. Transform lists with different function sequences, and shadow or filter lists of different lengths, also switch at the halfway point (there is no matrix decomposition) (Approximation). - Keyframes: a keyframe's own
animation-timing-functionis ignored (the element's timing is used for every segment). Implicitfromandtovalues are captured when the animation starts, so a later restyle does not update them. - Not animatable or not supported: custom properties and
@property,z-index,display,visibility, gradients,grid-template-*,animation-composition,animation-timeline, the Web Animations API, andprefers-reduced-motion. - Clock: animations use a monotonic real-time clock. Tests can freeze it (
--advance), but there is no host-visible pause.
Fonts
@font-face with local() and url() sources is supported (file paths relative to the stylesheet, absolute paths, file:///),
choosing the nearest registered weight and style, with later rules overriding earlier ones.
- TTF, OTF and TTC only. WOFF and WOFF2 are skipped with a warning because Skia cannot decode them; a build step or a decoder is needed.
unicode-range,font-display,size-adjust,font-synthesis, variable-font axes (font-variation-settings) andhttp(s)URLs are not supported. A weight range descriptor uses its first value.local()needs the exact installed family name.
Flexbox
align-items: baselinebehaves asflex-start(Approximation).flex-wrap: wrap-reversebehaves aswrap(Approximation).gapaccepts pixel-resolved lengths only (no%).place-items,place-content,place-selfshorthands are not parsed.- The automatic minimum size is zero for scroll containers and content-based otherwise, which matches the spec for the common cases.
Grid
- Not supported: named lines,
grid-template-areas,grid-area, thegridshorthand,grid-auto-flow: columnanddense, subgrid, masonry. fit-content()is approximated asautowith a max-content limit.repeat(auto-fill | auto-fit, ...)treats both identically (empty tracks are not collapsed forauto-fit).- Auto-placement is sparse row-major only.
Positioning and stacking
position: stickybehaves as static.- Stacking contexts exist for the root,
z-indexon positioned elements,position: fixed,opacity < 1,transformandfilter. A positioned element withz-index: autois painted at level 0 together with its positioned descendants (Approximation).isolation,mix-blend-modeandwill-changedo not create contexts. top: <percent>on relative boxes resolves against the containing block width (Approximation).- The static position of an absolute box in inline content, flex or grid is the start of the content box (Approximation).
margin: autocentring of absolutely positioned boxes (with both insets set) is not implemented.- A fixed box inside a scrolled context ignores that context's scrolling, which is correct. A transformed ancestor does not become the containing block of its fixed or absolute descendants, as it does in browsers (Approximation).
Scrolling
- Scrollbars are overlay thumbs only: not draggable, no track clicks, no arrow buttons (Planned).
scrollbar-widthandscrollbar-colorare ignored. - Wheel scrolling uses a fixed 80 px per notch, with no smooth scrolling, momentum,
scroll-behavior,scroll-snaporoverscroll-behavior. overflow: clipis treated ashidden.overflow: overlayis treated asauto.- No
onscrollevent is dispatched. NoscrollIntoViewAPI (Tab focus scrolls into view internally). - Hit testing uses rectangles and ignores
border-radius.
4. Input and events
See Input, events and form controls for the condensed version of sections 4 and 5.
Pointer
- Only the left button is dispatched. Right and middle buttons,
contextmenu,auxclick,dblclick(double-click is detected internally for text selection only) and touch are not dispatched. - Pointer Events (
onpointerdown,onpointermove, ...) are not dispatched. Only mouse events (click,mousedown/up/move/enter/leave/over/out,wheel). - No pointer capture, drag and drop, or gesture handling.
- Cursor mapping covers
pointer,text,crosshairand the horizontal/vertical resize cursors. Other keywords fall back to the arrow.
Keyboard
keydownandkeyupare dispatched.keypress,beforeinput,compositionstart/update/end, and clipboard events (copy,cut,paste) are not.- Printable
Keyvalues inkeydown/keyupassume a US layout. Typed text itself uses the OS character events and is layout-correct. KeyboardEventArgs.Locationis not set. Numpad keys report as digits.
IME (Unsupported on the current backend)
Committed IME text works, since it arrives as characters. The pre-edit (composition) text, its underline, and candidate-window positioning do not, because GLFW 3.3 as exposed by Silk.NET 2.x has no pre-edit callback. This needs a newer GLFW binding or a platform-specific integration.
Focus
- Sequential focus order is document order plus positive
tabindex. No focus trapping,autofocus, or:focus-within. FocusAsync()andElementReferencecapture are not supported (they need JS interop).
Accessibility (Unsupported)
No UI Automation, ARIA mapping, screen-reader support, or high-contrast handling. This is a significant gap for a production toolkit.
Text selection outside controls (Unsupported)
Static text (paragraphs, labels) cannot be selected or copied. Selection exists only inside input and textarea. user-select is ignored.
5. Form controls
- Text input and textarea: no context menu (right-click cut/copy/paste), no drag-and-drop of text, no spell check, no autofill. No right-to-left caret movement. Undo history is per field and lost when the element is removed. The textarea has no resize handle, and number inputs show no spinner buttons.
- Placeholder is a fixed gray and
::placeholdercannot restyle it. Selection color is derived fromaccent-colorand::selectioncannot restyle it. There is nocaret-color. - Input types not implemented:
range,color,file,date,time,datetime-local,month,week,image. They are edited as plain text or shown as an empty control. - Number input accepts any of
0-9 + - . e Ewhile typing (including incomplete values like1e), with no locale handling. - Validation:
required,pattern,min/max(beyond stepping),:valid,:invalidand the constraint validation API are not implemented.resetbuttons do nothing. - Select: single selection only. No
multiple,sizelist boxes,optgroupheadings ordatalist. The dropdown is capped at 10 visible rows and always uses a light theme. - Checkbox: no indeterminate state. Radio groups are matched by
namewithin the sameform. label: the activated control is the one named byforor the first labelable descendant.- Form submit: the
submitevent is dispatched. There is no navigation or form-data collection. - Binding race: two-way binding relies on handlers updating state synchronously. A handler that awaits before assigning can briefly overwrite fast typing, because the renderer does not use event field info to reconcile.
6. Blazor hosting
See Blazor hosting for this section on its own.
| Limit | Notes |
|---|---|
| No JS interop | IJSRuntime is not registered. Components that inject it fail. This rules out Virtualize, InputFile, FocusAsync, and most third-party component libraries. |
| No routing | @page is ignored, and Router and NavigationManager are not provided. The demo menu switches components with state. |
ElementReference | @ref on elements is ignored (reference-capture frames are skipped). |
| Markup frames | Static HTML emitted by the Razor compiler is parsed by a minimal fragment parser: no implicit tag closing, no table fix-up, entities via HtmlDecode only. |
EditForm and validation components | Untested. The submit event path works for plain <form @onsubmit>. |
| Hot reload | Untested. |
| Service provider | Only logging is registered. Add services by extending UiHost. |
For where each of these areas lives in the source, see Architecture.