drawnui/drawnui.net/drawnui-fluent
Claude Code and agent skill by drawnui/drawnui.net
- community
- ext:[{"type":"hook","description":"Pre/post lifecycle hooks"}]
Checking that the SKILL.md file is still in the repository.
Use when writing DrawnUI C# code-behind with fluent extensions. Covers inline control construction, .Assign(out _field), .Initialize(), .OnTapped(), .OnHovered(), .OnTextChanged(), .ObserveProperty(), .ObserveProperties(), .ObservePropertyTwoWay() (two-way binding), .Adapt(), .WhenPaint(), .ObserveSelf(), layout aliases (SkiaStack/SkiaRow/SkiaLayer), one-shot and looping animations, gradients, colors, shadows, SkiaLottie, SkiaImageTiles, and SkiaBackdrop code-behind patterns. Load before any DrawnUI C# composition task.
Install
npx vskill@latest install drawnui/drawnui.net/drawnui-fluentSemi-extensibleMentions customization, but needs skill-specific knowledge. Read the extensible skills standard
SKILL.md
DrawnUI Fluent Extensions — C# Code-Behind
Containers — use semantic aliases
Compose with alias containers, not raw SkiaLayout { Type = LayoutType.X }: SkiaStack (vertical, Fill), SkiaRow (horizontal), SkiaLayer (absolute overlay, Fill), SkiaWrap (Fill), SkiaMasonry (Fill, 2 columns), SkiaGrid (Fill). Aliases preset Type AND HorizontalOptions=Fill (except SkiaRow) — raw SkiaLayout defaults to Type=Absolute with NO fill, so it aligns differently. SkiaFrame = SkiaShape rectangle, not a layout.
Constructor shorthand is the documented idiom for text controls: new SkiaLabel("text"), new SkiaRichLabel("**md**"), new SkiaButton("Caption").
Row does NOT distribute Fill children (device-verified 2026-08-15): two HorizontalOptions=Fill children inside a SkiaRow each take full width and overflow the screen — a Row is not a star-grid. For proportional button rows use SkiaGrid + .WithColumnDefinitions("2*,3*") + .SetGrid(col, 0) per child. Same for HorizontalFillRatio inside a Row — not a substitute for grid columns. A child that is not Fill does get what is left, as in a SkiaStack vertically: a wrapping label after a title wraps in the remaining width, and an End child sits at the row's end. Pick containers in the order SkiaLayer (alignment + Margin), SkiaStack, SkiaRow, SkiaWrap, and SkiaGrid last (drawnui skill, "Choosing a container").
Composition Style — MANDATORY
Never declare a local variable for a control and then reference it in a children list. Always construct inline inside the collection initializer. Use .Assign(out _field) when a field reference is needed — it returns the control so chaining continues.
Do not spam `AddSubView(...)` while building a known static scene or container subtree. When the children are known at composition time, declare them together inside Children = new List<SkiaControl> { ... }. Reserve AddSubView() for dynamic/runtime operations such as pooled controls, generated rows, incremental updates, or add/remove flows that happen after the initial tree is built.
Critical: Static vs Runtime Children Mutation
Before layout is ready (static composition in constructor):
After layout is ready (runtime mutation, e.g. in RefreshFriendsList):
Build the full list first and assign once (verified 2026-09-16: popup wrappers built as Children = new List<SkiaControl>() followed by Children.Add(x) never rendered and their ScaleToAsync never completed). The collection-initializer form Children = { a, b } (no new List) adds to the control's own observed collection.
Rule of thumb: Use Children = new List<...> only during initial construction. Once LayoutIsReady has fired or the control is in the visual tree, use ClearChildren(), AddSubView(), RemoveSubView(): they work whatever collection Children holds.
WRONG — never do this:
RIGHT — always inline:
Nested layout:
Children = { ... } (bare collection initializer) and .WithChildren(a, b, c) are equivalent inline forms — fine too. .WithContent(child) sets the single child of IWithContent containers (e.g. SkiaScroll, ContentLayout). .AssignParent(parent) adds the control to a parent mid-chain.
WRONG for static composition:
RIGHT for static composition:
Chain after .Assign() — it returns the control:
Omit redundant default assignments like Left = 0, Top = 0. Keep explicit zeros only in reset/pooling paths where prior state may still be present.
Layout shortcuts: .Center() / .CenterX() / .CenterY() (control inside parent), .Fill() / .FillX() / .FillY(), .StartX() / .StartY() / .EndX() / .EndY(). Text alignment inside a SkiaLabel (NOT the label's own position): .CenterText() / .CenterTextX() / .CenterTextY() — set HorizontalTextAlignment / VerticalTextAlignment to Center.
Property shortcuts (all chainable): .WithHeight(n) / .WithWidth(n), .WithMargin(all) / (h,v) / (l,t,r,b) / (Thickness), .WithPadding(...), .WithCache(SkiaCacheType.X), .WithBackgroundColor(c), .WithHorizontalOptions(...) / .WithVerticalOptions(...), .WithVisibility(bool), .WithTag("name"); shape: .Shape(ShapeType.Circle); image: .WithAspect(TransformAspect.X); label: .WithFontSize(n), .WithTextColor(c), .WithHorizontalTextAlignment(...).
Grid placement helpers
Definitions are set on the grid itself (string form, chainable), placement on each child:
SetGrid(column, row) / SetGrid(column, row, columnSpan, rowSpan) — column comes first, opposite of the usual "row, column" convention (the XML doc comment on the 4-arg overload in FluentExtensions.Maui.cs / FluentExtensions.NonMaui.cs lists them backwards — trust the signature). WithColumnDefinitions / WithRowDefinitions return SkiaLayout, so put them last in the chain (after .Assign(...)). Individual: .WithRow(n), .WithColumn(n), .WithRowSpan(n), .WithColumnSpan(n). Also DefaultColumnDefinition.
.Initialize vs .Adapt
.Adapt(me => ...) runs setup on the control itself mid-chain. Do NOT access OTHER .Assign'd references from Adapt — they may not exist yet. Post-build wiring that touches assigned refs goes in .Initialize(me => ...) on the OUTERMOST control — it runs after the whole chain is constructed.
.Initialize fires once, when the control gets its parent (added to Children/Content or to the canvas), independent of IsVisible, measuring or virtualization (since 1.10.6.16; before it fired at first measure, so an IsVisible=false control never ran it). Never attached → runs at first measure. Superview may still be null inside; tree-ready work goes in LayoutIsReady / Initialized. Trap: a child gets its parent INSIDE the parent's object initializer, before the parent's own .Assign(out ...) runs, so an .Initialize on a child that reads a sibling/parent field sees null. The lazy-target observers (ObserveProperty(() => x, ...), ObserveProperties(() => x, ...), Observe(() => x, ...)) therefore resolve their target at the control's FIRST MEASURE, not on attach (attach-time resolution left every SkiaSlider style thumb unsized, 2026-09-23).
Event / Gesture Wiring — MANDATORY
Always use fluent extension methods — never += events or commands wired outside the initializer.
| Task | Fluent method | |
|---|---|---|
| Tap handler | .OnTapped(me => { ... }) | |
| Tap with args | .OnTapped((me, args) => { ... }) | |
| Long press | .OnLongPressing(me => { ... }) | |
| Mouse hover | .OnHovered((me, on) => { ... }) — turns ReceivesHover on; on = mouse over / left. A card and the button inside it are hovered together. Not tracked while content scrolls or slides (checked when it stops); touch never hovers | |
| Context menu (right click / long press / Menu key on the web heads) | .OnContextMenu((me, e) => true) — true = handled (browser menu suppressed), false = browser menu shows | |
Editor text changed (SkiaEditor) | .OnTextChanged(text => { ... }) | |
Editor focus (SkiaEditor) | .OnFocusChanged((me, focused) => { ... }) | |
| Label text + sender | .OnTextChanged((lbl, text) => { ... }) | |
| Arbitrary setup | .Adapt(me => { me.X = ...; }) | |
| Post-build wiring (touching Assign'd refs) | .Initialize(me => { ... }) | |
| Key pressed | .OnKeyDown((me, key) => { ... }) | |
| Key released | .OnKeyUp((me, key) => { ... }) | |
| Paint hook (before own background) | .WhenPaint((me, ctx) => { ... }) | |
| Overlay above content + children | .WhenPainted((ctx, control) => { ...; return false; }) — true = keep repainting | |
| Self-observe any property | .ObserveSelf((me, propName) => { ... }) | |
| Templated layout applied a collection change | .WhenItemsSourceChangesApplied(me => { ... }) — after every ItemsSource change (adds, removes, moves, LoadMore pages), once per frame, after the frame that shows it. Under MeasureVisible appended rows are measured later: read sizes in MeasurementApplied | |
Raw gesture interception (SkiaLayout-derived) | .WithGestures((me, args, apply) => { ... }) — return me = consumed, null = pass; never consume Up unless required |
Keyboard: .OnKeyDown / .OnKeyUp both take (control, InputKey key) — InputKey.ArrowLeft/ArrowRight/ArrowUp/ArrowDown, Space, Enter, KeyD… Attach them to the ROOT control of the tree, not to the focused child. Verified on the Fiddle WASM build 2026-08-30.
OnKeyDown repeats at the OS key-repeat rate — with a delay before the first repeat and gaps between them — so driving a value directly from it produces stepped, laggy movement. For anything held (a game paddle, a camera pan), keep a direction flag instead and move per frame:
On Blazor the key event is not preventDefaulted, so Space also scrolls the hosting page while the canvas has focus.
`.WhenPaint` draws BEFORE the control paints its own background (verified 2026-08-30, DrawnUI Fiddle WASM). A control that has BackgroundColor set will cover everything the hook drew — silently, no error, blank result. Put the background on a parent (e.g. a SkiaShape frame) and attach .WhenPaint to a transparent child filling it:
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 90 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingdocs/skills/drawnui-fluent/SKILL.md
Popularity
- 7-day trend score
- 0
Not refreshed yet.
Extension points
hookPre/post lifecycle hooks
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/drawnui/drawnui.net/drawnui-fluent)