AI Decision System
Replayable, offline-verifiable records for AI-assisted go/no-go decisions: experiment promotions, typed model answers and human-approved actions.
Choose the right DrawnUI runtime for .NET MAUI, Blazor, or platform-agnostic rendering workflows.
Developer toolsC#GitHub @DrawnUiOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Rendering engine for .NET MAUI, Blazor, OpenTK and pure .NET powered by SkiaSharp 🎨
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
DrawnUI is a rendering and UI composition engine for .NET, powered by SkiaSharp with gestures, layouts, effects and animations running with hardware acceleration.
• DrawnUi.Maui - Android, iOS, MacCatalyst, and Windows.
• DrawnUi.Blazor.Wasm - browser WebAssembly rendering.
• DrawnUi.Blazor.Server - server-backed DrawnUI surfaces served by Blazor Server.
• DrawnUi.Wasm - pure browser WebAssembly, no Blazor required.
• DrawnUi.OpenTk - Windows and Linux desktops.
• DrawnUi.Net - platform-agnostic console/server rendering scenarios.Read the full README ↗DrawnUI is a rendering and UI composition engine for .NET, powered by SkiaSharp with gestures, layouts, effects and animations running with hardware acceleration.
Supported hosts:
DrawnUi.Maui - Android, iOS, MacCatalyst, and Windows.DrawnUi.Blazor.Wasm - browser WebAssembly rendering.DrawnUi.Blazor.Server - server-backed DrawnUI surfaces served by Blazor Server.DrawnUi.Web - pure browser WebAssembly, no Blazor required.DrawnUi.OpenTk - Windows and Linux desktops.DrawnUi.Wpf - drawn controls inside WPF windows.DrawnUi.Net - platform-agnostic console/server rendering scenarios.DrawnUI for React just appeared as a standalone DrawnUI engine in TypeScript, running on CanvasKit (Skia compiled to WebAssembly) in the browser. It tends to use same API as the .NET version. Under active development, more info on our site.
DrawnUI for Rust is the same engine in Rust, drawing with Skia on Windows, macOS, Linux, iOS, Android and in the browser. Same controls and rules as the .NET version, one crate to add (drawnui on crates.io), in preview. Try the demo at hellorust.drawnui.net, more info on our site.
DrawnUi.Maui.Game, DrawnUi.Blazor.Game, DrawnUi.Web.Game, DrawnUi.OpenTk.Game, DrawnUi.Wpf.Game.DrawnUi.MauiGraphicsDrawnUi.Maui.MapsUiDrawnUi.Maui.Camera - Separate repo.👉 Docs and Samples
🤖 AI skills
🤩 Fiddle
⛹️ Pong in pure WASM
SkiaMasonry (LayoutType.Masonry) lays items of different heights in columns without gaps: every item goes to the bottom of the shortest column, like a photo feed. Set Split for the number of columns. It works with children and with ItemsSource: recycled cells, LoadMore pages that measure only the new items, items that grow after they are shown (an image arrives), ScrollToIndex, taps, and keyboard arrows that move to the tile on screen that way. See Layouts.Split) and SkiaWrap grids, down and back up. A cell that changes size after it is shown, any number of times, moves the cells below it.SkiaRow reports the items really on screen in FirstVisibleIndex / LastVisibleIndex and lays out with every MeasureItemsStrategy, MeasureVisible included. A templated SkiaWrap inside a scroll fires LoadMore at its end and arrives at ScrollToIndex.ImageDoubleBuffered caches render in the background without touching the frame being drawn.SkiaCarousel swipe lands on the next or the previous slide, however far the finger drags and however fast it flicks; ScrollTo and SelectedIndex still go anywhere.Xamarin.AndroidX.Core version an app resolves, including 1.19.DrawnUi.Maui no longer brings the AppoMobi.Maui.Native.Android package; an app that uses its types references it directly.TranslationZ moves a control toward you or away from you (closer is bigger), also when it is not turned, and with RotationX / RotationY it turns cards and panels in 3D with perspective. Cached content stays smooth in 3D and under any rotated, scaled or skewed parent, and taps still reach the right control. A SkiaBackdrop in a scaled, moved, rotated or 3D-turned parent shows exactly what is behind it, and the log tells you once when it sits in a picture-cached SkiaShape (set UseCache = None on that shape for live glass). Changed behavior in 1.10.7.12: before, TranslationZ only changed the strength of the perspective.Canvas close cleanly (#361); a finger tap on iOS 26 is a plain tap; native views inside a SkiaMauiElement (a MAUI Button, a TapGestureRecognizer, any UIView) complete their taps (#374). On Mac Catalyst an app that sets a desktop window size and declares a scene manifest becomes active smoothly. On every platform a SkiaMauiElement that starts hidden shows its native view when it becomes visible (#372).SkiaShaderCarousel moves one more slide and Stopped comes once, when it really stops; after a tap on a SkiaCarousel, IsUserFocused clears when the finger lifts. A scroll inside a drawer keeps its gesture, a short drag held still before lifting goes back, a drawer opened in the same moment it becomes visible stays open, and a drawer tapped while it moves finishes its move instead of stopping halfway. SnapVelocityThreshold sets how fast a release must be to count as a flick, for drawers and carousels. PopModalAsync closes any modal in the stack, also one hidden under another modal, and modals hidden under a modal that froze the background come back when it closes. A modal swiped away leaves the stack when the finger lifts, and the new ModalRemoved event comes when a modal is fully gone.MIT | Free to use and customize
DrawnUI is built and maintained by Nick Kovalsky, who is available for commercial work: full mobile and desktop app development, custom controls, performance and rendering work, Xamarin → MAUI migrations, and support contracts. Get in touch via LinkedIn or taublast(at)gmail.com.
ShouldInvalidateParentCache = true rebuilds it on every change), UseCache = GPU uses the GPU on every head, WebAssembly and OpenTK included, drawing a frame creates far fewer objects (69.6 KB down to 12.9 KB per frame while scrolling uncached cards), so the garbage collector runs less often; a control that keeps updating behind another screen (a camera preview under a pushed page) leaves the canvas idle, iOS frames show on the very next refresh, controls measure once when properties change in bursts, an ImageComposite cache redraws only the area of a deep change, and SafeAction runs on time under a hidden parent.Padding change made while a control's cache is drawn in the background is applied before the next frame, as on MAUI, so a SkiaSlider track reaches its thumb.ItemsSourceChangesApplied comes after every change (add, remove, move, every LoadMore page). Templated grids (Split) add a LoadMore page without rebuilding the cells they have, and MeasureVisible grids place their rows right after the first screen. Drag to reorder keeps every row drawn in MeasureAll lists.UseVulkan = false keeps OpenGL.Auto column wraps and its row grows. Text with CharacterSpacing stays inside its label when it is cut to fit, and no text is cut at in-between screen scales (a margin of 18 at scale 1.25). Japanese and Chinese text wraps properly, symbols and emoji inside normal text use FontFamilyFallback (one font or a list), people can select and copy a label's text (AccessibilityTextSelectable), and AutoSize = FitHorizontal grows back when there is room. On .NET MAUI Windows lines have the same height as on the other platforms, so all-caps text sits centered.UIApplicationSupportsIndirectInputEvents = true to Info.plist). A right click or the Menu key reaches SkiaControl.ContextMenu on MAUI Windows and OpenTK. Hover: ReceivesHover, HoverChanged and .OnHovered((me, on) => ...) let any control take mouse hover, and every hovered control under the mouse stays lit (a card and the button inside it). Hover waits while a list scrolls and is checked again when it stops.SkiaShape / SkiaLabel works on the control's own content (UseCache = None on the control for a see-through effect); RespondsToGestures = false switches input off on every interactive control (SkiaButton.IsDisabled is obsolete), and on .NET MAUI IsEnabled = false switches a drawn control and everything inside it off (#368); in a SkiaWrap a Fill child without WidthRequest takes a whole line; SkiaStack and SkiaRow honor ZIndex; LockChildrenGestures works on layouts; SpeedRatio means the real speed (0.5 is half); on Android with UseDesktopKeyboard text fields keep their keys; templated layouts default to MeasureItemsStrategy="MeasureAll" (set MeasureFirst for rows of one height).