Declarative XUI

The .xui language describes a retained control tree with C# state and behavior. The compiler generates C# that uses the existing XUI bindings. It does not add a runtime parser, virtual tree, or reconciler.

The implementation supports fixed compositions. It supports named control styles, named visual parts, state rules, typography, and color resources. The control inventory describes the available presentation surfaces. It does not support arbitrary dynamic children, control templates, or custom row templates. The engineering plan defines the implementation and acceptance checks. The XUI Designer provides a native editor and live preview for a self-contained component.

Author a component

namespace Demo;

component Counter {
    state int Count = 0;

    view {
        VStack(spacing: 8, padding: 16) {
            Text($"Count: {Count}", id: "count");
            Button("Increment", click: Increment, id: "increment");
        }
    }

    code csharp {
        void Increment() => Count++;
    }
}

The namespace is optional. Each file declares one component. A component produces a C# class. The view block describes the native tree. The code csharp block supplies C# methods. Fields belong in state declarations.

Leaf controls end with a semicolon, for example Text("Hello");. Containers have a child block and end with its closing brace, for example HStack() { Text("Hello"); }. Do not add a semicolon after a child block or after a view, resources, style, or code csharp block.

Native layout nodes are VStack, HStack, Grid, AdaptiveLayout, ScrollView, Popup, SplitView, and Reveal. Basic nodes are Text, Button, Toggle, ToggleSwitch, ToggleButton, CheckBox, HyperlinkButton, InfoBadge, and TextInput. Other native nodes are DataGrid, NavigationView, ItemsView, RangeInput, Progress, ProgressRing, SelectorBar, and MenuBar. SwapChainPanel is a Windows-only graphics leaf. Its generated reference exposes the native graphics contract. Content embeds an existing element. Stacks have no positional argument. Each other native node requires a string argument. Most arguments use C# expressions. Style arguments use the bounded style syntax described below.

All nodes support ref: Identifier, size: (width, height), and preferredSize: (width, height). Size values use DIPs. ref produces a typed public property for the native element. Reference names cannot conflict with parameters, state, methods, the component name, or Root. Names that start with __xui are reserved.

Stacks support spacing and uniform padding. Controls support id, enabled, visible, and help. Hidden ScrollView controls reserve no desired layout space, including passthrough hosts. The id argument supplies the automation ID. help supplies native help text. Stack, Grid, AdaptiveLayout, and Content are elements, not controls, so they do not support those four arguments. For example, Button("?", size: (36, 36), help: "Row 1, column 1: covered."); declares a square cell.

Text, Button, Toggle, ToggleSwitch, and ToggleButton use their positional string for both text and the accessible name. They do not accept a separate name argument because those native properties share storage. TextInput has a separate accessible name and text value. Its text, change, and submit arguments configure that input. It also supports captionVisible and placeholder. Toggle and ToggleSwitch support checked and change. ToggleSwitch keeps the Toggle model and accessibility role, with a switch pill and thumb. ToggleButton supports checked, change, and icon. It selects Button toggle behavior by default. Its change handler receives the checked state as a bool. Toggle actions do not also emit a click event. The node does not accept click. Checked-state setters are silent for both toggle presentations. Event arguments name C# methods. Button supports icon: global::Xui.ButtonIcon.Refresh through the native SetIcon method. Button also accepts the reactive boolean verticalText (default false). For example, Button("Hierarchy", verticalText: true, size: (32, 112)); draws a sideways edge tab: the full label rotates clockwise, while its accessible name and keyboard behavior remain unchanged. ButtonIcon.Pin and ButtonIcon.PinFilled supply outlined and filled pushpins. NavigationView supports headerVisible. Its searchId and searchHelp arguments configure the native search input. Its optional reactive duration argument is a uint from zero through 10,000 milliseconds. Omission preserves the zero-duration default. Changed values call SetDuration without replacing the native search input. The group animation contract describes logical state, focus, and interruption. SplitView supports secondVisible. Its optional reactive duration argument accepts a uint from zero through 10,000 milliseconds. Zero disables motion. The compiler initializes duration before secondary visibility. The animation contract describes pane geometry, focus, and interruption. Popup supports placement: global::Xui.PopupPlacement.Right and windowBackground. AdaptiveLayout takes navigation first and content second, with exactly two retained children. It supports reactive presentation, navigationSide, navigationExtent, navigationOpen, dismissOnFocusOutside, breakpoint, and compactNavigation. Its positional name is a fixed constructor input, not reactive state. See the layout examples for responsive and pinned/overlay presentation. DataGrid accepts a global::Xui.GridColumn[] expression in columns. The compiler calls SetColumns when the authored column values change.

Compiler diagnostics

XUI syntax and validation errors use XUI001 and point to the authored .xui file, line, and column. Messages identify the relevant declaration, control, argument, or style part and explain the correction. For example, a semicolon after HStack() { ... } reports:

Unexpected ';' after the child block of 'HStack'. Remove the semicolon: blocks end with '}', not '};'.

The location points to the extra semicolon, outside the child block. Missing delimiters name the block or argument list being parsed and identify the unexpected token, including end of file. Unknown arguments and unsupported style keywords list the choices allowed in that context. Value errors describe the required literal form or numeric bounds. Duplicate names and mismatched style references identify the conflicting declaration or target.

Builds and Designer previews use the same compiler diagnostics. Invalid source still blocks compilation; a helpful message does not silently accept or repair invalid syntax. C# type and name errors retain their C# diagnostic codes and map back to the authored expression or method. C# syntax errors reported through XUI retain the compiler's explanation with added declaration or expression context.

Opt-in bottom reveal

Reveal retains exactly one child and slides it within a native clip. Its positional string supplies the accessible name. The open argument is a reactive bool. The duration argument is a reactive uint in milliseconds. The reactive layout and direction arguments accept global::Xui.RevealLayout and global::Xui.RevealDirection. Their defaults are Fixed and Bottom. The compiler initializes duration, layout, and direction before open state. Zero is the default duration and disables motion. The native setter rejects values greater than 10,000 without changing the previous duration.

component FindExample {
    state bool FindOpen = false;
    view {
        VStack() {
            Button("Open Find", ref: OpenFind, click: ShowFind);
            Reveal("Find bar", open: FindOpen, duration: 180,
                layout: global::Xui.RevealLayout.Expand, ref: FindReveal) {
                HStack(padding: 6, spacing: 8) {
                    TextInput("Find", ref: Find, captionVisible: false,
                        placeholder: "Find", preferredSize: (320, 44), flex: 1);
                    Button("Close Find", click: HideFind, size: (44, 44));
                }
            }
        }
    }
    code csharp {
        void ShowFind() { FindOpen = true; Find.Focus(); }
        void HideFind() { FindOpen = false; OpenFind.Focus(); }
    }
}

This example animates the reserved height together with the content position. An automatic grid row or non-flex stack child follows that measurement. The default Fixed layout instead reserves full height at entry start and releases it after exit. Top, Left, and Right select other entry edges. Horizontal expansion needs content with a bounded natural width. The application does not bind child visibility to the open state, because outgoing content remains visible during exit. The application still chooses focus immediately. Reversal, reduced motion, native input, and performance limits follow the reveal contract. The typed reference exposes read-only Progress and Animating properties. These properties are not .xui arguments. Reveal has no style target or arbitrary transform arguments.

CheckBox accepts checkState, threeState, and change. The state type is global::Xui.CheckState, with Unchecked, Checked, and Indeterminate values. The change method accepts one CheckState parameter. The default state is unchecked, and three-state input is off. This node does not change the binary checked and boolean change contracts of Toggle and ToggleSwitch.

HyperlinkButton accepts click and icon. Its callback does not imply URI or browser navigation. The application owns any external action.

SelectorBar accepts a global::Xui.Choice[] expression in items and a nullable ulong expression in selected. Its change method accepts one ulong parameter. The compiler applies items and selection as one native snapshot. For joint reactive replacements, use one tuple state that contains both values. Separate state assignments refresh immediately and can expose an invalid intermediate selection. The SelectorBar example shows the tuple pattern.

InfoBadge accepts count as a uint, or icon as a global::Xui.ButtonIcon. The compiler rejects both arguments on one node. Without either argument, the badge uses its default dot presentation. The badge has no action event.

MenuBar accepts a global::Xui.Command[] expression in commands. Its invoke and pin methods each accept one ulong command ID. Root records must have Kind: global::Xui.CommandKind.Submenu. Malformed snapshots preserve the previous commands. The native window manages submenu popups and keyboard input.

All five nodes require a positional accessible-name string. They accept common layout, identity, visibility, help, reference, and style arguments. Style aliases map CheckBox to Toggle, HyperlinkButton to Button, SelectorBar to ChoiceList, InfoBadge to InlineStatus, and MenuBar to CommandBar. InfoBadge count text uses part message, not part title. The control guides include complete examples.

Range input and progress

RangeInput is the native slider control. Its name is not Slider. Progress is a read-only native progress control. ProgressRing uses the same range, value, and state model with a circular presentation. All three require a positional accessible-name string and accept the common control, layout, reference, and style arguments. All three are leaf nodes.

component VolumeMeter {
    state double Level = 25;
    view {
        VStack(spacing: 8) {
            RangeInput("Volume",
                range: new global::Xui.NumericRange(0, 100, 1, 10),
                currentValue: Level,
                orientation: global::Xui.Axis.Horizontal,
                reversed: false,
                change: SetLevel);
            Progress("Volume level",
                currentValue: Level,
                progressState: global::Xui.ProgressState.Determinate);
        }
    }
    code csharp {
        void SetLevel(double value) { Level = value; }
    }
}
Argument Type Controls
range global::Xui.NumericRange RangeInput, Progress, ProgressRing
currentValue double RangeInput, Progress, ProgressRing
orientation global::Xui.Axis RangeInput
reversed bool RangeInput
change Method with one double parameter RangeInput
progressState global::Xui.ProgressState Progress, ProgressRing

The currentValue argument sets the numeric value. The language reserves value for the positional accessible-name operand, so value: remains invalid. Numeric values, orientation, reversal, and progress state support ordinary reactive expressions. The compiler checks expression and handler types through C#. Progress states are Determinate, Indeterminate, Paused, Error, and Unknown. Indeterminate progress uses native, window-owned animation while attached, visible, and effectively enabled. Hidden or minimized windows stop that animation. The system client-area animation preference suppresses motion. Unknown states and capacity displays remain static. The progress contract defines timer ownership and lifecycle.

The optional range expression runs once during component construction, before the first numeric value setter. It cannot depend on component state or call component methods. It can use constructor parameters and supported state-free C# expressions. This rule does not make parameter objects or external objects immutable. Later external changes do not trigger a range update. Changes to the authored range expression require structural replacement during hot reload.

An omitted range retains native bounds from 0 through 100, with small step 1 and large step 10. An omitted numeric value retains the native initial value. With a supplied range, native construction can clamp that initial value to its bounds. The generator does not subsequently write a synthetic zero. The omitted orientation is horizontal, and reversal is false. Progress defaults to determinate state. ProgressRing defaults to indeterminate state.

Native setters require finite increasing bounds, a finite interval span, and positive finite steps. Numeric values must be finite and inside the range. Progress and ProgressRing check the supplied range structure but use only its minimum and maximum for display. Invalid values and enum values produce native errors, without generator coercion or silent recovery. As with other reactive bindings, a failed native setter does not roll back the authored C# state assignment. The next valid state update can restore the control value.

The change handler receives committed RangeInput changes as a double. Property setters do not call this handler. The generated component registers the handler once, not during property refresh. Native drag previews and cancellation retain their existing behavior but do not call this committed-change handler. This language extension does not add preview or cancellation handlers. Progress and ProgressRing do not accept a change handler.

Named styles use the existing RangeInput and Progress catalog targets and named parts. ProgressRing also uses the Progress target. ToggleSwitch and ToggleButton use the existing Toggle and Button targets. The foundation reference describes the native range, input, and progress contracts.

Declare control styles and resources

Resources and named styles belong directly inside a component:

component DangerActions {
    resources {
        DangerFill: theme(light: 0xB42318, dark: 0x8F1D16);
        OnDangerFill: 0xFFFFFF;
        DangerEdge: theme(light: 0x68110C, dark: 0xFFA198);
        DangerHover: theme(light: 0xD92D20, dark: 0xB42318);
        DisabledFill: theme(light: 0xD0D5DD, dark: 0x475467);
        DestructiveFill: resource(DangerFill);
    }

    style DangerButton for Button {
        background: resource(DangerFill);
        foreground: resource(OnDangerFill);
        cornerRadius: 0;
        borderBrush: resource(DangerEdge);
        borderThickness: (3, 0, 0, 0);
        when hovered { background: resource(DangerHover); }
        when disabled { background: resource(DisabledFill); }
    }

    view {
        VStack() {
            Button("Delete", style: DangerButton);
        }
    }
}

The sample DeclarativeSample\Counter.xui includes this Delete button beside the existing counter controls. The button demonstrates appearance and does not delete data.

Grammar

The following grammar describes the style structure. The exported catalog restricts each target, part, property, state, and value combination. Identifier uses the same identifier syntax as other XUI names. Brackets mark optional syntax, and braces after = mark repetition. Quoted braces are literal delimiters.

Resources   = "resources" "{" { Identifier ":" Color ";" } "}"
Style       = "style" Identifier "for" Target [ "basedOn" Identifier ]
              "{" { Property | Rule | Part } "}"
Part        = "part" PartName "{" { Property | Rule } "}"
Rule        = "when" State "{" { Property } "}"
Property    = PropertyName ":" Value ";"
Color       = Rgb24
            | "theme" "(" "light" ":" Rgb24 "," "dark" ":" Rgb24 ")"
            | "resource" "(" Identifier ")"
Insets      = Dimension | "(" Dimension "," Dimension "," Dimension "," Dimension ")"

Target, PartName, State, and PropertyName must match the supported catalog names. Value must match the property's type and the part's limits. Targets use case-sensitive PascalCase names, such as TextInput, ItemsView, and NavigationView. Part, property, and state names use lower camel case. The target aliases are Text for Label and VStack or HStack for Stack. The style contract describes shared behavior and target-specific boundaries. Root and part rules accept only states that the corresponding native model supplies. Support for a property and a state does not imply support for that property inside the state rule. For example, ItemsView accepts base tile.width, but rejects tile.width in every state rule. Parts cannot contain other parts. State blocks cannot contain parts. All styles reject duplicate parts and duplicate state blocks within one part. The root is implicit, so part root is invalid. Base and derived styles must target the same control type. Per-part local values use the C++, C ABI, C#, or Rust setter. Style-value and part-rule edits update the method-body revision for hot reload. They preserve the existing control tree.

The compiler accepts the Element-applicable targets in the exported catalog. Tooltip declarations reject because tooltips require the Window API. ContentDialog, CommandSurface, LocationPicker, and ViewPicker are facades, not style targets. Their root styles use Popup. Retained children use their actual control targets.

Property values

The catalog determines which properties each target and part accept. This vocabulary does not imply that every property applies to every part.

Properties Value syntax
background, foreground, borderBrush RGB24 integer, resource(Name), or theme(light: RGB24, dark: RGB24)
padding, borderThickness One dimension or four dimensions in left, top, right, bottom order
fontFamily Nonempty C# string literal with valid Unicode and no NUL
fontSize Positive numeric literal, limited by the part
fontWeight Integer literal from 1 through 999
fontStyle Bare normal, italic, or oblique, limited by the part
horizontalAlignment, verticalAlignment Bare start, center, end, or stretch, limited by the part
wrapping true or false
maximumLines Integer literal from 0 through 32768
cornerRadius, size, spacing, headerHeight, indentation, thickness, width, height, rowGap, columnGap Dimension
rowHeight Positive dimension

Font families have a maximum length of 1024 UTF-8 bytes and must also fit the part's UTF-16 limit. Native text parts restrict font size to 512 DIPs and family length to 31 UTF-16 code units. Those native parts accept normal or italic fonts, not oblique. Paragraph parts reject vertical stretch. Supported layout parts can accept stretch. Enum values are unquoted names, not strings or C# enum expressions.

Rgb24 is an integer literal from 0x000000 through 0xFFFFFF. Hexadecimal values use 0xRRGGBB, not alpha or COLORREF byte order. Decimal and other C# integer literal forms are also valid within that range. Dimension is a finite numeric literal from 0 through 32768 DIPs. Negative values, arithmetic expressions, state references, and method calls are not style values. Insets use left, top, right, bottom order. A single dimension applies to all four edges. Parenthesized expressions, named tuple elements, and two-value inset shorthand are not supported.

The theme(light: ..., dark: ...) order is fixed. This explicit pair keeps light and dark values together without separate theme blocks or ambiguous override order. It also permits each resource alias to identify one complete color. The generator preserves both colors in ThemeColor. The native renderer selects the active theme at paint time. The generator does not read the current theme or freeze the color during construction.

All resources blocks in one component form one color scope. Resource names are case-sensitive and must be unique within that scope. Aliases and style properties can refer to resources declared later in the component. The compiler checks every resource, including unused resources. Cross-component resources, mutable resource dictionaries, and other resource types are not supported.

Sparse values, derivation, and local properties

A style sets only its declared properties. An omitted property remains absent, not zero. Explicit zero therefore differs from an omitted corner radius, inset, or black color. State rules also contain sparse values. Repeated properties within the same style body or rule are errors. All declarations reject duplicate state blocks within one part. Rules cannot contain other rules.

A derived style names its base after for Button:

style CompactDanger for Button basedOn DangerButton {
    padding: (8, 2, 8, 2);
    when pressed { background: 0x68110C; }
}

Base styles can appear later in the component. Style names must be unique and cannot conflict with generated members, state, parameters, references, or methods. Resources and styles have separate name scopes. The compiler rejects missing base styles and inheritance cycles.

Native nodes accept a declared style name in style: Identifier. The control catalog lists all 24 supported nodes. The style target must match the node. Other supported targets use Content(existingElement, style: NamedStyle), without a new constructor syntax. Native attachment checks the actual target of that existing element. Content requires a named style before it accepts local style properties. Button styles can also be applied to existing buttons through Content.

Style names are XUI references, not fields available inside code csharp. A Button also accepts the six foundation properties directly as named arguments:

Button("Delete", style: DangerButton, padding: (8, 2, 8, 2), cornerRadius: 0);
Button("Local only", background: resource(DangerFill), foreground: 0xFFFFFF);

Local properties use the same constant syntax as style properties. Every style declaration produces a ControlStyle, and local properties produce sparse PartStyleValues. Generated code uses Element.SetControlStyle and Element.SetControlStyleValues. Local properties use the root part's supported property set. The native engine resolves local overrides, state rules, base styles, and defaults.

The node argument size: (width, height) remains structural size, not the scalar style property size. Button scalar size belongs only to its supported icon and arrow parts. Stack node arguments padding and spacing remain structural float setters and override style values. Omitted Stack arguments leave style values available. Explicit zero overrides them. Padding inside a style declaration still accepts four-edge insets.

xui
style Heading for Label {
    foreground: theme(light: 0x202020, dark: 0xEEEEEE);
    fontFamily: "Segoe UI";
    fontSize: 20;
    fontWeight: 600;
    wrapping: true;
    maximumLines: 2;
}
style Panel for Stack { padding: 12; spacing: 8; }

Text("Title", style: Heading) and VStack(style: Panel) apply these declarations inside the component's view. These declarations do not replace the controls or their input behavior.

The style contract describes that precedence and the native rendering boundaries. This stage does not change control ownership, events, keyboard behavior, accessibility, or the control tree. It does not implement ItemTemplate, control templates, implicit styles, arbitrary selectors, or state expressions.

Bounds and diagnostics

A component supports at most 256 color resources. A style supports at most 256 rules. Style inheritance supports at most 16 layers, including the applied style. Resource aliases can traverse the bounded component scope but cannot form a cycle. The compiler checks unused styles and resources as well as referenced declarations.

Invalid declarations produce XUI001 at the .xui source location and a mapped C# error that blocks the build. These errors include duplicate names, missing references, cycles, unsupported targets, properties, states, invalid literal types, and values outside the limits. Invalid inset lengths, duplicate properties, and excess resources, rules, or inheritance layers also block the build. General control expressions outside the style subset retain their existing C# diagnostics.

Shared definitions and reload

The generated class shares immutable style definitions across its component instances. The managed binding creates native handles for each owning window. Components without named styles do not allocate a style cache.

Edits to existing colors, aliases, properties, state rules, base-style references, and applied style references can update existing controls. The generated cache checks a revision from a method body during refresh. A changed revision creates new shared definitions and applies them to the existing elements. Unchanged definitions do not trigger another native style assignment. Local value edits update the existing local override binding. These refreshes preserve component state, input state, event subscriptions, and control ownership.

Resource and style declaration names and order belong to the structural signature. Adding, deleting, renaming, or reordering those declarations requires replacement. Adding or removing a style binding or local property also requires replacement. This prevents a removed property from remaining on a retained element. The general runtime restrictions in reload behavior still apply.

Reuse a component

component BrowserPane {
    param global::Xui.Element Files;
    param string Title;

    view {
        Grid(Title, ref: Layout,
            rows: [new(global::Xui.TrackSizing.Star, 1)],
            columns: [new(global::Xui.TrackSizing.Fixed, 200), new(global::Xui.TrackSizing.Star, 1)]) {
            NavigationView("Locations", ref: Navigation, headerVisible: false);
            ScrollView("Files", column: 1) {
                Content(Files);
            }
        }
    }
}

param Type Name; declares an immutable constructor input and a public read-only property. Parameters have no initializer. The constructor accepts Window window, the parameters in declaration order, and bool attach = true. The parameter names window and attach are reserved. Every component exposes its typed native root as Root.

var pane = new BrowserPane(window, files, "Browser", attach: false);
var root = window.Stack().Add(pane.Root, flex: 1);
window.SetContent(root);

With attach: false, the constructor creates the tree without calling SetContent. With attach: true, the root must be VStack or HStack. A different root causes an ArgumentException before the constructor creates controls. Existing calls such as new Counter(window) still attach their Stack root.

Content(expression); uses the supplied Element without creating another native element. The expression runs once during construction and cannot depend on state. The native bindings reject content from another window or content that already has a parent. The caller must keep constructor-supplied element identities fixed for the component lifetime.

Grid accepts global::Xui.GridTrack[] expressions in rows and columns. Each omitted argument defaults to one star track. The compiler installs tracks before it adds children. Track expressions can depend on state. The positional Grid name is a constructor input and cannot depend on state. Unlike controls, the managed Grid has no name setter.

A direct Grid child supports integer row, column, rowSpan, and columnSpan arguments. Rows and columns start at zero. Spans default to one and must be positive. The native Grid rejects cells outside its tracks. A direct Stack child supports flex: float. Placement and flex cannot depend on state because the bindings expose no placement update method. Content also accepts these parent-placement arguments.

Grid, VStack, and HStack use braces for their children. ScrollView and Popup require exactly one child inside braces. SplitView and AdaptiveLayout require exactly two children. The compiler creates children before it calls factories that require those children.

Bind state

The compiler generates a property for each state declaration. A state change updates properties that directly depend on that state. It does not reevaluate the entire view. Initializers do not run again during an in-place reload. Equal assignments do not update the controls. Mutations inside a reference-type state value do not trigger updates. Assigning a replacement state value can trigger an update.

Direct state references make dependencies explicit. An arbitrary method can hide a state dependency from the compiler. The first version rejects unsupported view expressions instead of adding runtime dependency discovery. External helpers must be pure and receive their state dependencies as explicit arguments. Pure methods on a state value, such as Game.CellText(0), also expose that state dependency. The compiler does not prove the purity of arbitrary external C# code. Member names on external types do not identify component methods. For example, ButtonIcon.Search remains valid when the component also declares a Search handler. Direct calls to component methods, including this.Search(), remain invalid in view expressions.

Configure a project

The Xui NuGet package includes the compiler and runtime. The dotnet new xui template creates a package-based project with a counter and Debug hot reload. The package guide describes package-based integration. For source development, use the projects in this repository:

  1. Create a .NET 10 executable project.
  2. Import bindings\dotnet\Xui.Declarative.targets after the project's property definitions.
  3. Add the .xui files inside the project directory.
  4. Create the component before Window.Run.

The following project uses a placeholder path to the repository:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <RuntimeIdentifier>win-arm64</RuntimeIdentifier>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
  <Import Project="D:\src\xui\bindings\dotnet\Xui.Declarative.targets" />
</Project>

The targets include the compiler and managed binding references. Debug builds also include the development host. Release builds exclude that host. The targets supply the native-control application manifest unless the project specifies another manifest.

The entry point can select the development host at compile time:

using Xui;

internal static class Program
{
    [STAThread]
    private static void Main()
    {
#if XUI_HOT_RELOAD
        Xui.Development.ReloadHost.Run(
            () => new Window("Counter", 480, 360),
            window => _ = new Demo.Counter(window));
#else
        using var window = new Window("Counter", 480, 360);
        _ = new Demo.Counter(window);
        window.Run();
#endif
    }
}

By default, the component constructor creates its controls and installs the Stack root content. All control access uses the creating UI thread. The development host does not change that rule.

Run the sample

Use the C# build and watch commands. Edit bindings\dotnet\DeclarativeSample\Counter.xui while the watcher runs. The native library does not rebuild for each markup change.

Understand reload behavior

Authored text and supported property expressions can update existing controls. Stack spacing and padding can also update in place. Existing size and help bindings can update in place. Other supported property bindings, including tracks and columns, can also update in place. Supported C# handler-body edits affect later events. The development host applies refreshes on the UI thread.

In-place refresh preserves component state. It does not attach each event handler again. Unchanged authored input values do not overwrite user edits. Unchanged authored arrays do not reset tracks or user-adjusted column widths. The compiler compares array contents, not array identities. Omitted control properties do not generate refresh setters.

A structural edit changes the control types or their parent-child relationships. A source edit to the state schema, an initializer, or an explicit automation-ID expression also requires replacement. Parameter declarations, reference names, placement, flex, Grid and AdaptiveLayout names, and Content expressions belong to the structural signature. An edit to any of them requires replacement. Adding or deleting an event subscription requires replacement. Adding or removing an optional binding also requires replacement. Changing the target method of an existing event subscription can update in place. The development host reports window replacement and resets component state. It closes the old window, waits for Run to return, and disposes the old owner. Then it constructs a new window.

The .NET runtime can reject other code changes as unsupported live edits. With --non-interactive, the watcher restarts the process for those edits. Process restart also resets transient state.

Controls without explicit IDs use positional identity. Reordering same-kind controls without IDs can retain native input state at the old positions. Stable literal IDs distinguish stateful controls during source edits.

Recoverable property errors produce diagnostics without a partial update. The old window remains usable for those errors. An error that prevents generation of a component's declarations can cause the SDK to request a process restart. A later valid edit recovers without deletion of generated files, but a restarted process loses transient state.

Publish a native executable

Use the NativeAOT instructions. The published executable excludes the development reload host. The executable and xui.dll must use the same architecture.

Play Minesweeper

The Minesweeper sample uses .xui for a complete game interface. It includes first-click safety, flood reveal, flags, win/loss states, and restart. Its immutable C# model supplies values for a fixed native board.

After the native build, run:

dotnet watch --project bindings\dotnet\Minesweeper\Minesweeper.csproj --non-interactive

Install VS Code syntax support

The extension is in integrations\vscode-xui. Its README contains the package and installation commands.

The extension supplies .xui highlighting, embedded C# highlighting, brackets, comments, and snippets. It is a syntax package. It does not provide a language server, semantic completion, or a visual designer.

Microsoft Edit syntax support

The XUI LSH grammar supplies .xui highlighting for Microsoft Edit. It recognizes declarations, control names, named arguments, styles, resources, and embedded C# tokens. It preserves multiline comments, verbatim strings, raw strings, and nested C# blocks. Ordinary and verbatim interpolated strings use C# highlighting inside interpolation expressions. Their interpolation braces use the normal code color, while escaped braces remain part of the string. Named argument labels have a separate token category from identifier values. Hexadecimal and binary literals accept either letter case, digit separators, and integer suffixes. Raw interpolated strings use one string color. Their interpolation expressions are not parsed. A delimiter-length quote run inside a raw interpolation can end highlighting early.

The grammar is a standalone file with no dependency on other LSH definitions. It uses existing Edit highlight kinds and registers the XUI display name for *.xui files. Highlighting is lexical, not semantic validation. It does not check control arguments, style catalog entries, or C# types. Incomplete multiline strings, comments, and blocks retain their context until a closing delimiter.

Edit compiles LSH definitions into its executable. This repository does not install the grammar into an existing Edit binary. The integration procedure describes compilation and the regression suite.

Run the integration checks

Use the compiler and integration commands. The fixture reference describes the native probes and isolated edit loop.

Performance boundary

The baseline is equivalent handwritten C# that uses the same managed bindings. The bindings still allocate and marshal data for native calls. The language does not remove those existing costs.

The ordinary state-update path uses generated, targeted property calls. Development refresh tracking does not belong in the release application. The release application does not load the compiler or a UI interpreter.

The managed Debug build supplies the development loop. NativeAOT publishing is a separate release operation. An incremental generator does not guarantee a fixed save-to-visible-change duration for every application.