A GPU-accelerated desktop UI framework for .NET 10, written in plain C#
SkiaSharp rendering · Silk.NET windowing · Material Design 3
Atelier is a retained-mode UI framework for desktop applications. You build the interface in C# with a fluent, type-safe markup API instead of XAML, and every control is drawn with SkiaSharp on an OpenGL surface.
- C# only. No XAML, no CSS, no web view. Views are ordinary C# with IntelliSense, refactoring and hot reload.
- Material Design 3 out of the box. Light and dark color schemes, the MD3 type scale, default styles sized for desktop use, and 2,100+ Material Symbols with all four variable font axes.
- A real property system. Bindable properties with value precedence (default, inherited, style, local, animation, coerced), styles, attached properties, typed data binding and change notifications.
- Built for low overhead. Rendering reuses cached paints, fonts and text blobs, so a steady frame allocates nothing, and windows only redraw when something changed.
- Reflection-free tooling. Commands (
[Command]: name, label, description, icon and default shortcut), keybindings and property-grid metadata come from source generators, as do theMaterialIconsname constants for icons given as text.
All screenshots show the included Gallery application.
using Atelier.Controls;
using Atelier.Core.Primitives;
using Atelier.Layout;
using Atelier.Markup;
using Atelier.Platform.Silk;
using Atelier.Theming;
using Atelier.Theming.Material;
using CommunityToolkit.Mvvm.ComponentModel;
ThemeManager.Current = MaterialTheme.CreateLight();
var vm = new GreetingViewModel();
var window = new SilkWindow(title: "Hello Atelier", width: 640, height: 400);
// A factory lambda enables hot reload: the view is rebuilt from the same view model.
window.SetContent(() =>
new StackPanel()
.Spacing(16)
.Margin(32)
.Children(
new TextBlock("Welcome to Atelier").HeadlineMedium(),
new TextBox()
.Label("Your name")
.LeadingIconKind(MaterialIconKind.Person)
.MaxWidth(320)
.HorizontalAlignment(HorizontalAlignment.Left)
.BindText(vm, v => v.Name, (v, name) => v.Name = name),
new TextBlock().BodyLarge().BindText(vm, v => $"Hello, {v.Name}!"),
new Button("Say hello")
.HorizontalAlignment(HorizontalAlignment.Left)
.OnClick(() => vm.Name = "World")));
window.Run();
public partial class GreetingViewModel : ObservableObject
{
[ObservableProperty]
private string _name = "Atelier";
}Reference Atelier.Markup and Atelier.Platform.Silk; they bring in the rest of the framework.
Every property has a fluent method with the same name that returns the element, so calls chain and keep their type:
new Button("Save").Variant(ButtonVariant.Tonal).Command(vm.SaveCommand).IsEnabled(false);
new Grid().Columns("Auto,*").Spacing(12, 8).Children(label.Cell(0, 0), field.Cell(0, 1));Bind{Property}(source, s => s.Value)binds to an object;Bind{Property}((MyViewModel vm) => vm.Value)binds to theDataContext. An optional setter makes the binding two-way.On{Event}(...)attaches handlers; events without data also accept anAction.- Controls get their size, shape and colors from the theme, so views only set what they change.
| Control | Highlights |
|---|---|
Button, RepeatButton, ToggleButton |
Filled, tonal, elevated, outlined and text variants; commands with CanExecute; a registered command's icon, label and tooltip (description and shortcut) when no content is set; click modes; repeat delay and interval; two- and three-state toggles |
CheckBox, RadioButton, Switch |
Three states, rich content, named and unnamed radio groups, thumb icons, value-matching radio binding |
TextBox |
Outlined and filled variants, floating label, placeholder, leading icon, supporting text, INotifyDataErrorInfo validation, max length, password mode, undo/redo, clipboard and word navigation; multi-line text areas (AcceptsReturn, TextWrapping, MinLines/MaxLines, scrolling) |
ComboBox |
Items or bound collections, typed item templates, text search, keyboard navigation, max drop-down height, compact mode |
Slider, Knob, ProgressBar |
Ranges, small and large steps, tick snapping, formatted value bubble; a rotary knob (Atelier.Audio) turned by dragging (Shift for fine steps), the wheel or the keys, reset by double-clicking; determinate and indeterminate progress |
ListBox, ItemsControl |
UI virtualization (only the items in view get containers), incremental updates from observable collections, typed templates, type-to-search, keyboard navigation |
TreeView |
Data-bound or item-built trees, children selector, typed templates, configurable expander icons; the shown nodes form one virtualized list |
DataGrid |
Virtualized rows; text, template and check box columns; multi-column sorting; Pixel, Auto and Star widths; resizing, reordering and a column chooser with saved layouts; a filter row and spreadsheet-style filter menus; single, check box and extended selection of rows or cells; F2 editing with custom editors and validation; Ctrl+C as tab-separated text; row context menus and empty placeholders |
OpenFileDialog, SaveFileDialog, FolderBrowserDialog |
The same dialog on every platform, with WPF's properties (Filter, Multiselect, CheckFileExists, OverwritePrompt, InitialDirectory, ...): path bar with clickable folders, folder tree, file grid with optional attribute columns, new folder and rename; any file system through IFileSystemProvider |
Menu, MenuItem, ContextMenu |
Menu bars (also in the title bar), submenus and context menus; icons, check and radio items, separators; header, icon and shortcut text from the command's [Command] declaration; CanExecute disables or hides items; access keys; menus built from data with a children selector and item templates |
TabControl |
MD3 primary and secondary tabs with a sliding indicator; browser-style tabs that close, add and reorder by dragging; header and content templates |
DatePicker, TimePicker |
MD3 docked and modal calendars and date input; clock dial and text input with 12- and 24-hour clocks; typed dates and times, ranges and validation; compact mode |
ColorPicker |
Hue/saturation wheel, RGB, HSL and HSB sliders that show their gradients, alpha, hex and channel inputs |
Badge |
MD3 small dot and large count or text badges on any element |
Card, Border |
Outlined, elevated and filled cards; elevation shadows; per-corner radii; clipping |
Popup, Dialog, DialogHost |
Smart placement with flipping (or at the pointer), light dismiss, dialog presets and custom buttons, async results, scoped hosts |
ToolTip, RichToolTip |
.ToolTip("text") on any element; rich tooltips hold any element and stay open for their buttons and links; placement, delays, disabled elements, optional keyboard-focus tooltips |
ScrollViewer |
Per-axis scroll bar modes, wheel and keyboard scrolling, scroll events |
TransitioningContentControl |
Fade, slide, zoom, slide-and-fade and composite transitions with configurable duration and easing |
PropertyGrid |
Categories (alphabetical, or in a given order with CategoryOrder), sorting, filtering, validation, custom editors per type or predicate, a resizable label column; metadata generated at compile time |
GridSplitter |
Resizes grid columns or rows by dragging or with the arrow keys; keeps star proportions; min/max limits, snapping, preview mode, Escape to cancel, double-click to restore |
Icon, Image, TextBlock |
Material Symbols with variable axes, SVG path data or whole SVG documents (Source: an icon name or SVG); stretch modes; wrapping, trimming, max lines and line height |
TitleBar, Toolbar, KeybindingHandler |
Custom window chrome, action bars, scoped shortcuts with chords, and pointer gestures bound like keys ("RightClick", "Ctrl+WheelUp", "MiddleDrag" running an IDragCommand); a handler can run further groups for a fixed target; users can change a command's label, icon and shortcut (KeybindingManager.SetCustomization, saved and restored as JSON) |
KeybindingEditor, ShortcutRecorder, ShortcutView |
Lets users change every command's label, icon and shortcut, listed by group (or all, or the changed ones), with search, conflicts and reset; records shortcuts and chords from key presses, and clicks, drags and wheel turns; shows shortcuts as key caps |
CommandPalette |
Searches the commands whose shortcuts work where the focus is (KeybindingHandler.GetActiveCommands: the handlers from the focus up to the window), ranks label prefixes, word starts and initials first, and runs the chosen one on its target |
NodeEditor |
Blender-style node graphs (Atelier.NodeEditor): typed sockets with opt-in conversions, links as curves, value controls beside unconnected inputs; Blender-style editing (select, box select, move with snapping, drag to connect or move links, delete, duplicate, collapse, mute, a searchable add menu and a context menu; drop a node on a link to insert it, Alt+drag to detach it, drop a link on empty space to search for a node to connect, Ctrl+right drag to cut links, Shift+right drag to add reroute points); auto-arrange (Shift+L: a layered layout in columns along the links, with few crossings, clusters of closely linked nodes kept together, links led around nodes through reroute points and unlinked parts packed compactly, as one undo step); reusable node groups (Ctrl+G to make one, Tab to edit it, with its inputs and outputs in a panel); saving as JSON and copy and paste with every action a rebindable command; grid and dot backgrounds, undo, and optional dataflow evaluation |
TimelineRuler, TimelineLane |
A DAW-style beat and time ruler and track lanes (Atelier.Audio) connected through a shared TimelineContext (zoom, scroll, tempo map, sample rate), so zooming or scrolling one moves all; ticks and labels adapt to the zoom in bars and beats (5, 5.2, 5.2.3, following tempo and time signature changes), time (01:05, milliseconds only when zoomed in) or samples, with an optional second row in another unit; Bitwig-style zooming by dragging the ruler, wheel zoom and scroll, and a menu to choose the units, all rebindable commands; lanes draw the matching grid lines behind their content. Colored marker flags above the ruler (shared by the song and drawn across every lane, or a lane's own), a loop bar below it, a play start marker and a playhead, with positions pinned to seconds or to beats; click to set the play start, drag flags and loop edges (snapping to the grid in view, Shift inverts it), double-click to add or rename markers or toggle the loop, right-click to recolor or delete |
TimelinePanel, WaveformView |
Clips on a track (Atelier.Audio): the panel places each child at an attached start and length in seconds or beats, following zoom, scroll and tempo changes and skipping clips out of view; the lightweight waveform draws part of a sound from a shared WaveformData (an exact min/max peak pyramid and running sums for RMS, so drawing costs the same at any length) like a DAW: a filled peak envelope with the RMS inside when zoomed out (smoothed over 25 ms, so low notes don't turn into moiré), the waveform's own shape closer in, and a line through the samples with a dot per sample at the deepest zoom; one lane per channel or combined |
AudioWaveformEditor |
Non-destructive editing of one sound on the timeline (Atelier.Audio), like a DAW clip: drag the edges to trim (the trimmed parts stay dimmed), square handles to fade in and out (linear or equal-power), and a dB chip to change the gain; drag to select, trim to the selection, and a menu; everything snaps to the grid and is a bindable property, and GetGain() gives the same fade and gain curve (WaveformGain) for playback; the waveform is drawn at the level they give it |
WorkspaceView, AreaLayout, Area |
Blender-style areas and workspaces: register editor types (id, title, icon, category, content and header factories) in an AreaEditorRegistry, and every area gets a button in its top-left corner to switch its editor (switching back finds the editor as it was). Drag from an area's corner into it to split it, into a neighbor to join that neighbor, or with Ctrl onto another area to swap them; drag borders to resize; right-click a border for the Area Options (split, join, swap) or a header for split, Maximize Area (Ctrl+Space) and Close Area. Workspaces are browser-style tabs that switch arrangements (Ctrl+Page Up/Down), added from templates, renamed by double-click, duplicated (Layout.001), deleted and reordered from their menu; AreaDefinition describes layouts in code and saves them as JSON |
Viewport3D |
GPU-rendered 3D (Atelier.Graphics3D): a Scene3D of MeshInstance3Ds (a Mesh3D of triangles, lines or points with normals, texture coordinates and vertex colors, a transform and a Material3D: base color and sRGB texture, tangent-free normal map, roughness, metalness, translucency for backlit fabric, unlit, double-sided, drawn on top) rendered with OpenGL 3.3 on the window's context into a 4× multisampled floating-point buffer, lit by a sun (GGX highlights) and a sky and ground ambient, tone-mapped (Khronos PBR Neutral) over a background gradient; an infinite ground grid, an axis gizmo and shaded, wireframe or shaded-wireframe drawing; Mesh3D.UpdatePositions animates every frame; redraws only on changes. Blender-style navigation with an OrbitCamera: middle-drag orbit, Shift+middle-drag pan, wheel zoom towards the pointer, Alt+drag without a middle button, Home to frame, numpad views and orthographic, all rebindable commands; a placeholder without a GPU |
XYChart |
2D line and scatter charts for engineering data (Atelier.Charts): XYSeries with solid, dashed or dotted lines and circle, square, diamond or triangle markers; axes with titles, nice ticks (1, 2, 5 × 10ⁿ, as many decimals as the step needs, in the current culture), grid and zero lines; labeled points (ChartAnnotation) in rounded labels that place themselves around the point clear of other labels, the legend and the curves; a legend in the emptiest corner; a readout of the nearest point under the pointer. The view fits the data (and follows new points) until you zoom or pan: the wheel zooms at the pointer (Ctrl only horizontally, Shift only vertically), dragging pans, right-dragging zooms into a box, + and − zoom, Home or a double click fits again, all rebindable commands; lines skip what is out of view, so thousands of points stay fast |
StackPanel, WrapPanel, DockPanel, Grid (Auto, pixel and star sizes, spans), UniformGrid, Canvas and
VirtualizingStackPanel (realizes only the items in view and reuses their containers; fixed or measured heights), plus
margin, padding, alignment, min/max sizes, visibility, clipping, opacity, layout transforms and render transforms. Any
element can set the mouse cursor (.Cursor(CursorType.Hand)); text boxes show the I-beam and splitters the resize arrows.
MaterialTheme.CreateLight()/CreateDark(): MD3 color schemes, switchable at runtime in every open window.MaterialColorScheme.FromSeed(accent, isDark, variant)generates a complete light or dark scheme from one accent color (tonal spot, vibrant, fidelity, neutral or monochrome), with every "on" color at 4.5:1 contrast or more.- Theme default styles follow the MD3 specs with a desktop density (32 px buttons, 48 px text fields);
MaterialSizing.Touchgives the standard MD3 sizes. - Styles apply by type or by key (
.StyleKey(...), typography shortcuts such as.TitleLarge()), with the precedence theme style < app style < local value. - Focus rings appear for keyboard navigation only.
In Debug builds, press F12 in any window to open the DevTools beside the window's content (behind a
GridSplitter); press it again to put the window back as it was. Release builds contain none of it. F12 and
Ctrl+Shift+C are ordinary commands of the "DevTools" window group (KeybindingManager.WindowGroups: shortcuts that work
anywhere in a window), so they appear in menus, the command palette and the keybinding editor, and can be rebound.
- Element tree of the window and its open popups, with search; hovering a node highlights its element.
- Select Element (Ctrl+Shift+C): the element under the pointer is highlighted live, a click selects it; Escape stops.
- Properties: every bindable property of the selected element, attached ones included, edited in a
PropertyGrid(thicknesses and corner radii as text:8,8,4or1,2,3,4). - Values: all values with their source (local, style, inherited, default, animation), filterable.
- Layout: the box model (margin, border, padding, content, spacing) drawn over the window and as a diagram, plus position, desired size, alignment, min/max sizes and layout state.
- Zoom: the selected element magnified pixel by pixel (up to 32×), with a pixel grid, box-model outlines and the color of the pixel under the pointer.
- Outline every element's bounds, show the frame rate, select the parent or the focused element.
- Borderless windows with native resizing, snapping and shadows on Windows; multiple windows with per-window focus.
- Render-on-demand: idle windows don't draw.
- Game controllers:
Gamepads.First/Gamepads.GetState(i)give sticks (+Y up), triggers (0–1) and buttons in the Xbox layout, from GLFW's gamepad support (Xbox and most other controllers);Gamepads.ApplyDeadzonefor dead zones. SilkWindow.Closingintercepts every close request (close button, Alt+F4, taskbar,Close()): cancel it, or pass an async decision such as a "Save changes?" dialog toe.Defer(...);Close(force: true)skips it.- Hot reload through .NET Hot Reload, or manually with F5 or Ctrl+R when the app doesn't handle those keys itself.
- System clipboard, key repeat, and an optional frame-rate overlay (
SilkWindow.ShowFpsOverlay).
| Project | Contents |
|---|---|
Atelier.Core |
Visual tree, bindable properties and bindings, styles, routed input, focus, animation, dispatcher, keybindings |
Atelier.Rendering |
SkiaSharp drawing context, paint, font and text caches, text measurement |
Atelier.Layout |
Layout panels and Border |
Atelier.Controls |
The control library |
Atelier.Theming |
Theme infrastructure and renderer registry, theme extensions for other libraries' controls |
Atelier.Theming.Material |
Material Design 3 renderers, color schemes, typography, sizing and default styles |
Atelier.Markup |
The fluent markup API |
Atelier.Generators |
Source generators for commands, keybindings and property-grid metadata |
Atelier.NodeEditor |
Node graph view models, undo, dataflow evaluation and the NodeEditor control (namespace Atelier.Nodes) |
Atelier.Audio |
Controls for audio tools: the Knob, and the timeline model shared by connected timeline controls (TimelineContext for zoom and scroll, TempoMap with tempo and meter changes, TimelinePosition in seconds or beats, and the adaptive beat, time and sample TimelineGrid), the TimelineRuler and TimelineLane, clips on a TimelinePanel, WaveformView with shared WaveformData, and the AudioWaveformEditor for trims, fades, gain and selections |
Atelier.Charts |
The XYChart with its series, annotations and axes (XYSeries, ChartAnnotation, ChartAxis), nice tick math (ChartTicks), zoom and pan commands, renderer and theme styles |
Atelier.Graphics3D |
The 3D scene model (Scene3D, Mesh3D, Material3D, Texture3D, OrbitCamera) and the OpenGL Viewport3D, which renders on the window's GPU context (IHostWindow.GraphicsDevice) and composites into the 2D frame |
Atelier.Platform.Silk |
Windows, OpenGL context, input and clipboard via Silk.NET |
Atelier.DevTools |
The F12 developer tools (Debug builds only) |
Atelier.Gallery |
Showcase application for every control |
Atelier.Tests |
Unit, layout, binding and rendering tests (1,530+) |
tools/MakeIcon.cs |
Makes the gallery's .exe icon (Assets/Icons/Atelier.ico) from the logo; run dotnet run tools/MakeIcon.cs after changing it |
dotnet run --project samples/Atelier.GalleryThe gallery has a page for every control group. Each page demonstrates the control's properties, events, bindings and states, with the code that builds it. Useful shortcuts:
| Shortcut | Action |
|---|---|
| Ctrl+F | Search the pages |
| Ctrl+T | Switch between the light and dark theme |
| Ctrl+Shift+F | Show the frame-rate overlay |
| Ctrl+Shift+P | Open the command palette: search and run the commands that work where the focus is |
| Ctrl+K, Ctrl+S | Customize the commands: labels, icons and shortcuts (saved in %APPDATA%\Atelier\Gallery) |
| F5 / Ctrl+R | Rebuild the window content (hot reload; F5 resets the demos on the Keybindings page) |
| F12 | Open or close the developer tools (Debug builds) |
| Ctrl+Shift+C | Select an element with the pointer in the developer tools |
To render pages to PNG files without opening a window, as for the screenshots above, set
ATELIER_GALLERY_SNAPSHOT=<output folder>. Optionally also set ATELIER_GALLERY_PAGES=0,3,7,
ATELIER_GALLERY_THEME=dark, ATELIER_GALLERY_SIZE=1280x800 and ATELIER_GALLERY_SEED=#006A6A[:Vibrant] (a theme
generated from that accent color). ATELIER_GALLERY_TOOLTIP=<n> opens the page's n-th
tooltip, and ATELIER_GALLERY_FILE_DIALOG=open|save|folder shows a file dialog limited to the repository (run it
from the repository folder), and in Debug builds ATELIER_GALLERY_DEVTOOLS=<text>[:tab] opens the developer tools
with the first element whose description contains the text selected. ATELIER_GALLERY_COMMAND_EDITOR=<Group>/<Name>
opens the command editor with that command selected, and ATELIER_GALLERY_COMMAND_PALETTE=<search> (* for none) the
command palette. ATELIER_GALLERY_ARRANGE=1 auto-arranges the page's last node editor and scrolls to it.
dotnet testThis project is licensed under the MIT License. Copyright (c) 2026 Markus Luedin.
























