A modular .NET library for rendering images from YAML templates with flexbox layout. Render-backend agnostic with SkiaSharp as the default backend. AOT-compatible, no reflection.
llms.txt-- concise project overview for LLM context windows (~450 lines)llms-full.txt-- comprehensive reference with all YAML properties, API details, and conventions (~1250 lines)
dotnet build FlexRender.slnx # Build entire solution
dotnet test FlexRender.slnx # Run all tests
dotnet test --filter "ClassName" # Filter by test class
dotnet test --filter "MethodName" # Filter by test method
UPDATE_SNAPSHOTS=true dotnet test # Regenerate golden snapshot imagessrc/FlexRender.Core/ # Core library (0 external dependencies)
Abstractions/ # ILayoutRenderer<T>, ITemplateParser, IResourceLoader
Configuration/ # ResourceLimits, FlexRenderOptions
Layout/ # Two-pass flexbox layout engine (LayoutEngine, LayoutNode, LayoutSize)
Units/ # Unit, UnitParser, PaddingValues, PaddingParser, MarginValue, MarginValues
Loaders/ # FileResourceLoader, Base64ResourceLoader, EmbeddedResourceLoader
Parsing/Ast/ # Template, CanvasSettings, TemplateElement, TextElement, FlexElement, TableElement, TableColumn, TableRow, etc.
Parsing/Nodes/ # Format-neutral node model (TemplateMapping, TemplateSequence, TemplateScalar, TemplateNode)
Parsing/Engine/ # Shared parsing engine (TemplateEngine, ElementParsers, ChartParsers, ShapeParsers, KnownProperties, NodePropertyHelpers)
TemplateEngine/ # TemplateProcessor, ExpressionLexer, ExpressionEvaluator, InlineExpressionParser, InlineExpressionEvaluator, FilterRegistry
Filters/ # ITemplateFilter, CurrencyFilter, NumberFilter, UpperFilter, LowerFilter, TrimFilter, TruncateFilter, FormatFilter
Values/ # TemplateValue hierarchy (StringValue, NumberValue, etc.)
src/FlexRender.Yaml/ # YAML facade: YamlDotNet -> neutral nodes -> Core engine (-> Core + YamlDotNet)
Parsing/ # TemplateParser (facade), YamlNodeConverter
src/FlexRender.Xml/ # XML facade: XDocument -> neutral nodes -> Core engine (-> Core only); RenderXml extension
Parsing/ # XmlTemplateParser, XmlNodeConverter
src/FlexRender.Http/ # HTTP resource loader (-> Core)
src/FlexRender.Skia.Render/ # SkiaSharp renderer (-> Core + SkiaSharp)
Abstractions/ # ISkiaRenderer, IFontLoader, IImageLoader, IFontManager
Rendering/ # SkiaRenderer, TextRenderer, FontManager, ColorParser, RotationHelper, BmpEncoder, BoxShadowParser, GradientParser
Loaders/ # FontLoader, ImageLoader
Providers/ # IContentProvider<T,O>, ImageProvider
src/FlexRender.Skia/ # Skia backend meta-package (renderer + providers)
src/FlexRender.QrCode.Skia.Render/ # QR provider for Skia (-> Skia + QRCoder)
src/FlexRender.QrCode.Svg.Render/ # QR provider for SVG (-> Svg)
src/FlexRender.QrCode.ImageSharp.Render/ # QR provider for ImageSharp (-> ImageSharp + QRCoder)
src/FlexRender.QrCode/ # QR meta-package (references all renderers)
src/FlexRender.Barcode.Skia.Render/ # Barcode provider for Skia
src/FlexRender.Barcode.Svg.Render/ # Barcode provider for SVG
src/FlexRender.Barcode.ImageSharp.Render/ # Barcode provider for ImageSharp
src/FlexRender.Barcode/ # Barcode meta-package (references all renderers)
src/FlexRender.SvgElement.Skia.Render/ # SvgElement provider for Skia (-> Svg.Skia)
src/FlexRender.SvgElement.Svg.Render/ # SvgElement provider for SVG (native)
src/FlexRender.SvgElement/ # SvgElement meta-package (references all renderers)
src/FlexRender.ImageSharp.Render/ # ImageSharp renderer (-> Core + SixLabors.ImageSharp)
Rendering/ # ImageSharpRenderingEngine, ImageSharpTextRenderer, ImageSharpFontManager
src/FlexRender.ImageSharp/ # ImageSharp backend meta-package (renderer + providers)
src/FlexRender.Svg.Render/ # SVG output renderer (-> Core)
src/FlexRender.Svg/ # SVG backend meta-package (renderer + providers)
src/FlexRender.Content.Ndc/ # NDC (ATM receipt) content parser (-> Core)
src/FlexRender.DependencyInjection/ # Microsoft.Extensions.DI integration
src/FlexRender.MetaPackage/ # Meta-package (core + all backends + DI)
src/FlexRender.Cli/ # CLI tool (System.CommandLine, uses all packages)
Commands/ # render, validate, info, watch, debug-layout
tests/FlexRender.Tests/ # Unit + snapshot tests
tests/FlexRender.Cli.Tests/ # CLI integration tests
tests/FlexRender.ImageSharp.Tests/ # ImageSharp visual snapshot tests
examples/ # Example YAML templates
FlexRender.Core (0 external deps)
^ ^ ^ ^
| | | |
FlexRender.Yaml FlexRender.Http FlexRender.Skia.Render FlexRender.ImageSharp.Render FlexRender.Svg.Render
^ ^ ^ ^
| | | |
Qr/Bar/SvgElement providers per renderer (Skia/Svg/ImageSharp)
| | |
FlexRender.QrCode / FlexRender.Barcode / FlexRender.SvgElement (meta)
| |
FlexRender.Skia / FlexRender.ImageSharp / FlexRender.Svg (backend meta)
|
FlexRender.DependencyInjection (Microsoft.Extensions.DI)
|
FlexRender.MetaPackage (references all)
Note: FlexRender.Xml depends only on FlexRender.Core -- the shared parsing engine (TemplateEngine + element/chart/shape parsers) lives in Core and operates on the format-neutral node model. FlexRender.Yaml = Core + YamlDotNet; FlexRender.Xml = Core only. Neither format package depends on the other.
SkiaSharp is a managed C# binding over the native Skia engine. On Linux (including CI runners and Docker), the native libSkiaSharp.so library is not bundled automatically and must be provided via a separate NuGet package.
When to add: If the project runs on Linux and SkiaSharp is not installed as a system-wide dependency (e.g., CI, Docker, serverless).
| Package | Use Case |
|---|---|
SkiaSharp.NativeAssets.Linux |
Standard Linux environments with system libs (fontconfig, freetype) |
SkiaSharp.NativeAssets.Linux.NoDependencies |
Minimal/Docker containers without system libs |
The native assets package is added only to executable projects (CLI, tests, examples) -- not to library projects like FlexRender.Skia.Render. Library consumers bring their own native assets for their target platform.
Similarly, FlexRender.HarfBuzz requires HarfBuzzSharp.NativeAssets.Linux on Linux. Add it to executable projects that use HarfBuzz text shaping.
Diagnostics: if DllNotFoundException: libSkiaSharp or DllNotFoundException: libHarfBuzzSharp occurs, verify the native library and its dependencies with ldd /path/to/lib*.so.
The rendering pipeline:
YAML Template
-> TemplateParser (YAML -> AST: Template with CanvasSettings + TemplateElement tree)
-> TemplateExpander (expand EachElement/IfElement to concrete elements based on data)
-> TemplateProcessor (resolve {{variable}} expressions in element properties)
-> LayoutEngine (two-pass: MeasureAllIntrinsics -> ComputeLayout -> LayoutNode tree)
-> SkiaRenderer (traverse LayoutNode tree -> draw to SKBitmap via SkiaSharp)
OR ImageSharpRenderer (traverse LayoutNode tree -> draw via SixLabors.ImageSharp)
The builder pattern provides modular configuration without mandatory DI:
// Without DI
var render = new FlexRenderBuilder()
.WithHttpLoader(configure: opts => {
opts.Timeout = TimeSpan.FromSeconds(60);
opts.MaxResourceSize = 20 * 1024 * 1024;
})
.WithBasePath("./templates")
.WithSkia(skia => skia
.WithQr()
.WithBarcode())
.Build();
byte[] png = await render.RenderFile("receipt.yaml", data);
// With DI
services.AddFlexRender(builder => builder
.WithSkia(skia => skia.WithQr().WithBarcode()));Templates can be parsed once and cached, then rendered with different data:
// Parse once (at startup)
var parser = new TemplateParser();
_templates["receipt"] = await parser.ParseFileAsync("receipt.yaml");
// Render many times (per request)
byte[] png = await render.Render(_templates["receipt"], data);- Pass 1 -- Intrinsic Measurement (
MeasureAllIntrinsics): Bottom-up traversal computesIntrinsicSize(MinWidth, MaxWidth, MinHeight, MaxHeight) for every element. UsesTextMeasurerdelegate for content-based text sizing. - Pass 2 -- Layout (
ComputeLayout): Top-down traversal assigns positions and sizes, producing aLayoutNodetree with (X, Y, Width, Height).
| Stage | Key Classes |
|---|---|
| Configuration | FlexRenderBuilder, SkiaBuilder, FlexRenderOptions, ResourceLimits |
| Abstractions | IFlexRender, IResourceLoader |
| Parsing | TemplateParser, Template, CanvasSettings, TextElement, FlexElement, QrElement, BarcodeElement, ImageElement, SeparatorElement, TableElement, TableColumn, TableRow, EachElement, IfElement, ContentElement |
| Template Engine | TemplateExpander, TemplateProcessor, ExpressionLexer, ExpressionEvaluator, TemplateContext, InlineExpressionParser, InlineExpressionEvaluator, FilterRegistry, ITemplateFilter |
| Layout | LayoutEngine, LayoutNode, LayoutContext, LayoutSize, IntrinsicSize, Unit, UnitParser, MarginValue, MarginValues, PaddingParser.ParseMargin |
| Rendering (Skia) | SkiaRender (IFlexRender impl), SkiaRenderer, TextRenderer, FontManager, ColorParser, RotationHelper, BmpEncoder, BoxShadowParser, GradientParser |
| Rendering (ImageSharp) | ImageSharpRender (IFlexRender impl), ImageSharpRenderingEngine, ImageSharpTextRenderer, ImageSharpFontManager |
| Providers | IContentProvider<T,O>, QrProvider, BarcodeProvider, ImageProvider |
| Loaders | FileResourceLoader, Base64ResourceLoader, EmbeddedResourceLoader, HttpResourceLoader |
| DI | ServiceCollectionExtensions.AddFlexRender() |
| Values | TemplateValue (abstract), StringValue, NumberValue, BoolValue, NullValue, ArrayValue, ObjectValue |
| Content Parsers | IContentParser, IBinaryContentParser, ContentParserRegistry, NdcContentParser |
- .NET 10, C# latest,
Nullable=enable,TreatWarningsAsErrors=true - AOT compatible --
IsAotCompatible=trueon library and CLI. No reflection anywhere. Use pattern matching (switchon concrete types) for type dispatch GeneratedRegex-- source-generated regex for AOT compatibilitysealedclasses -- all leaf/value/concrete classes must besealedsealed record-- for token types and small immutable datareadonly record struct-- for small value types (IntrinsicSize,LayoutRect,PaddingValues)- File-scoped namespaces --
namespace Foo.Bar; - XML documentation --
<summary>,<param>,<returns>,<exception>on all public APIs - Guard clauses --
ArgumentNullException.ThrowIfNull(),ArgumentException.ThrowIfNullOrWhiteSpace(),ObjectDisposedException.ThrowIf() - Immutability -- value types are
readonly, collections exposed asIReadOnlyList<T> - Thread safety --
ConcurrentDictionarywhere concurrent access is expected (e.g.,FontManager) - String comparison --
StringComparer.OrdinalIgnoreCasefor dictionaries keyed by names
All security limits are centralized in the ResourceLimits class (Configuration/ResourceLimits.cs) and configurable via the FlexRenderBuilder.WithLimits() method. Each property validates that values are positive and throws ArgumentOutOfRangeException on invalid input.
| Property | Default | Purpose |
|---|---|---|
MaxTemplateFileSize |
1 MB | YAML template file size |
MaxDataFileSize |
10 MB | JSON data file size |
MaxTemplateNestingDepth |
100 | Expression nesting |
MaxRenderDepth |
100 | Render tree recursion |
MaxImageSize |
10 MB | Image loading |
MaxFlexLines |
1000 | Maximum flex lines when wrapping |
Configure limits via builder:
services.AddFlexRender(builder => builder
.WithLimits(limits =>
{
limits.MaxRenderDepth = 200;
limits.MaxTemplateFileSize = 2 * 1024 * 1024;
}));Or directly when constructing a renderer:
var limits = new ResourceLimits { MaxRenderDepth = 200 };
using var renderer = new SkiaRenderer(limits);These limits exist to prevent abuse and resource exhaustion. Never remove or weaken them without explicit justification.
- Total tests: 1264+ (unit + snapshot + integration, including ImageSharp snapshot tests)
- Framework: xUnit with
[Fact]and[Theory]/[InlineData] - Assertions:
Assert.*from xUnit;FluentAssertionsin some tests - Naming:
MethodUnderTest_Scenario_ExpectedResult(e.g.,Parse_SimpleTextElement_ParsesCorrectly) - Class naming:
{ClassName}Tests - Organization: Mirrors source structure --
Tests/Values/,Tests/Layout/,Tests/Parsing/, etc. - Snapshot testing:
SnapshotTestBasewith golden images ingolden/. Pixel-by-pixel comparison withcolorThresholdandmaxDifferencePercent(0.0 = exact match). SetUPDATE_SNAPSHOTS=trueto regenerate - Pattern: Arrange-Act-Assert
All new features and non-trivial changes must be developed in separate branches. Never commit feature work directly to main.
- Branch naming:
type/short-description(e.g.,feat/qr-provider,fix/layout-overflow,refactor/font-manager) - Types:
feat,fix,refactor,build,test,docs,chore - Do NOT merge into
main-- leave the feature branch as-is after completing work. Merging is done manually by the maintainer or via GitHub PR - Do NOT use git worktrees -- work directly in the repository checkout. Worktrees add unnecessary complexity and cause issues with stash conflicts and asset path resolution
Binary assets (PNG images, fonts) are stored directly in Git (Git LFS was removed). When referencing images in README or documentation:
- Use
raw.githubusercontent.com:https://raw.githubusercontent.com/RoboNET/FlexRender/main/examples/output/receipt.png - Do NOT use
media.githubusercontent.com/media/...-- that is the LFS delivery path and now returns 404, since the files are no longer LFS-tracked - Asset paths:
examples/output/*.png,examples/assets/fonts/*.ttf,examples/assets/placeholder/*.png,examples/visual-docs/output/*.png
Conventional Commits: type(scope): description
- Types:
feat,fix,refactor,build,test,docs,chore - Scopes (optional):
parser,layout,renderer,ast,examples,cli - Description: lowercase, imperative mood
dotnet tool install -g flexrender-cliAfter installation the flexrender command is available globally:
flexrender render template.yaml -d data.json -o output.png
flexrender validate template.yaml
flexrender info template.yaml
flexrender watch template.yaml -d data.json -o preview.png
flexrender debug-layout template.yaml -d data.jsonThe CLI is fully AOT-compatible. To build a standalone native binary (no .NET runtime required):
dotnet publish src/FlexRender.Cli -c Release -r <RID> /p:PublishAot=trueCommon RIDs: osx-arm64, osx-x64, linux-x64, linux-arm64, win-x64.
| Channel | Install | Requires .NET? | Use case |
|---|---|---|---|
| dotnet tool | dotnet tool install -g flexrender-cli |
Yes | .NET developers |
| GitHub Release | Download binary from Releases | No | Everyone else (CI, Docker, non-.NET) |
| Build from source | dotnet publish /p:PublishAot=true -r <RID> |
Build-time only | Custom builds |
Native AOT binaries are attached to every GitHub Release for: osx-arm64, linux-x64, win-x64.
The release workflow (.github/workflows/release.yml) publishes all packages to NuGet.org on tag push (v*). When adding a new project to src/, it must be added to the pack list in release.yml.
Library packages (packed with --no-build):
| Category | Packages |
|---|---|
| Core | FlexRender.Core, FlexRender.Yaml, FlexRender.Xml, FlexRender.Http |
| Renderers | FlexRender.Skia.Render, FlexRender.ImageSharp.Render, FlexRender.Svg.Render |
| QR providers | FlexRender.QrCode.Skia.Render, FlexRender.QrCode.ImageSharp.Render, FlexRender.QrCode.Svg.Render |
| Barcode providers | FlexRender.Barcode.Skia.Render, FlexRender.Barcode.ImageSharp.Render, FlexRender.Barcode.Svg.Render |
| SvgElement providers | FlexRender.SvgElement.Skia.Render, FlexRender.SvgElement.Svg.Render |
| Content parsers | FlexRender.Content.Markdown, FlexRender.Content.Html, FlexRender.Content.Ndc |
| Extensions | FlexRender.HarfBuzz |
| Meta (backend) | FlexRender.Skia, FlexRender.ImageSharp, FlexRender.Svg |
| Meta (feature) | FlexRender.QrCode, FlexRender.Barcode, FlexRender.SvgElement |
| DI & all-in-one | FlexRender.DependencyInjection, FlexRender.MetaPackage |
CLI tool (packed separately, without --no-build -- PackAsTool requires publish):
FlexRender.Cli-- native PDB files are excluded via MSBuild target to keep package ~23MB
The release workflow uses NuGet trusted publishing via OIDC instead of long-lived API keys. Setup:
- Go to nuget.org -> username -> Trusted Publishing
- Add policy: owner/repo =
<owner>/SkiaLayout, workflow =release.yml, tag pattern =v* - Set
NUGET_USERsecret in GitHub repo settings (nuget.org username, not email) - Remove old
NUGET_API_KEYsecret after verifying trusted publishing works
dotnet run --project src/FlexRender.Cli -- render template.yaml -d data.json -o output.png
dotnet run --project src/FlexRender.Cli -- validate template.yaml
dotnet run --project src/FlexRender.Cli -- info template.yaml
dotnet run --project src/FlexRender.Cli -- watch template.yaml -d data.json -o preview.png
dotnet run --project src/FlexRender.Cli -- debug-layout template.yaml -d data.jsonGlobal options: -v/--verbose, --fonts <dir>, --scale <float>, -b/--backend <skia|imagesharp>
The --backend option selects the rendering backend: skia (default, full features) or imagesharp (pure .NET, no native deps, no QR/barcode/SVG element support).
Working directory matters: CLI resolves all relative paths (template, data, fonts, image src) from the current working directory. When running examples, cd into examples/ first, otherwise assets like assets/fonts/Inter-Regular.ttf won't be found.
- Prefer
Span<T>andReadOnlySpan<T>-- for slicing strings and arrays without allocation. UseAsSpan()instead ofSubstring() - Use
Memory<T>andReadOnlyMemory<T>-- when span needs to be stored or passed asynchronously ArrayPool<T>.Shared-- for temporary large arrays (>1KB). Always return viatry/finallyObjectPool<T>-- for expensive objects likeStringBuilder,SKPaint,SKFont- Avoid LINQ in hot paths --
foreachloops over arrays/lists are faster than.Where(),.Select(),.ToList() - Collection capacity hints --
new List<T>(capacity),new Dictionary<K,V>(capacity)when size is known StringBuilder-- for string building in loops instead ofstring.Replace()or+=- Struct enumerators -- prefer
foreachoverList<T>(uses struct enumerator) vsIEnumerable<T>(boxes)
| Component | Hot Path |
|---|---|
LayoutEngine |
ComputeLayout, MeasureAllIntrinsics, LayoutColumnFlex, LayoutRowFlex |
ExpressionLexer |
Tokenize -- called per {{expression}} |
ExpressionEvaluator |
Resolve -- called per variable reference |
TemplateProcessor |
ProcessText, ProcessEachBlock |
TextRenderer |
MeasureText, WrapText |
// ArrayPool for temporary buffers
var buffer = ArrayPool<byte>.Shared.Rent(minLength);
try
{
// use buffer[0..actualLength]
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
// StringBuilder pooling (consider ObjectPool for high frequency)
var sb = new StringBuilder(estimatedCapacity);- No LINQ in hot paths (
foreachinstead of.Where()/.Select()/.ToList()) - Collections have capacity hints where size is predictable
- Large temporary arrays use
ArrayPool<T>.Shared - String building uses
StringBuilder, not concatenation in loops -
Span<T>used for string/array slicing instead ofSubstring()/Array.Copy() - SKPaint/SKFont objects are reused or pooled, not created per-element
The FlexRender plugin for Claude Code lives in a separate repository: RoboNET/FlexRender-Marketplace.
When to update the plugin skills:
Any change to the following areas requires updating the corresponding skill in the marketplace repo:
| Changed Area | Skill to Update | Skill Path |
|---|---|---|
| YAML template syntax, element types, properties, expressions | template |
flexrender/skills/template/SKILL.md |
| C# API, FlexRenderBuilder, AST classes, NuGet packages, DI | template-csharp |
flexrender/skills/template-csharp/SKILL.md |
| Content parsers (Markdown, HTML, NDC), IContentParser API | content-formats |
flexrender/skills/content-formats/SKILL.md |
| CLI commands, options, output formats | template |
flexrender/skills/template/SKILL.md (CLI Reference section) |
Checklist for code changes:
- If YAML syntax changed — update
templateskill - If new element type added — update
templateskill (Element Types + Common Properties) - If template expressions changed — update
templateskill (Template Expressions section) - If C# public API changed — update
template-csharpskill - If NuGet package added/renamed — update
template-csharpskill (NuGet Package Selection) - If content parser changed — update
content-formatsskill - If CLI command added/changed — update
templateskill (CLI Reference section) - If
KnownProperties.csupdated — check iftemplateskill needs property table updates
Plugin structure:
FlexRender-Marketplace/
├── .claude-plugin/marketplace.json # Marketplace manifest
└── plugins/flexrender/
├── .claude-plugin/plugin.json # Plugin manifest
├── cli-version.json # CLI version requirements (auto-updated by CI)
├── hooks/hooks.json # SessionStart hook for CLI auto-install
├── scripts/
│ ├── run-hook.cmd # Cross-platform polyglot wrapper
│ └── ensure-cli.sh # CLI check/install script
├── skills/
│ ├── template/SKILL.md # YAML template authoring skill
│ ├── template-csharp/SKILL.md # C# integration skill
│ └── content-formats/SKILL.md # Content parsers skill
└── README.md
CI automation: The release.yml workflow automatically creates a PR in the marketplace repo to update cli-version.json when a new FlexRender version is released.
When debugging or testing the WASM playground (src/FlexRender.Playground/), use the agent-browser tool to interact with the running playground at http://localhost:5249/. This allows inspecting rendered output, checking browser console for errors, and verifying layout diagnostics without relying on the user to manually test.
Key WASM constraints:
SKTypeface.FromFamilyName()andSKFontManager.Default.MatchFamily()return objects with invalid native handles in WASM -- accessing ANY native property (.FamilyName,.FontStyle.Weight) causes an unrecoverableRuntimeError: memory access out of bounds- Guard all system font calls with
!OperatingSystem.IsBrowser() - Never dispose shared typefaces in
RegisterFont-- they may be referenced by variant caches
- Create AST model in
Parsing/Ast/(sealed class extendingTemplateElement) - Add parser function in
Parsing/Engine/ElementParsers.cs(Core) -- register inTemplateEngine._elementParsersdictionary - Register all properties in
Parsing/Engine/KnownProperties.cs(Core; for validation and typo suggestions) - Add flex-item property support via
switchpattern matching in layout engine - Add rendering in
SkiaRenderer.RenderNode()or create a provider - Write tests for each step
- Add token type as
sealed recordinExpressionToken.cs - Add lexer support in
ExpressionLexer.cs - Add evaluation in
TemplateProcessor.cs - Write tests
- Add property to
TemplateElementinParsing/Ast/TemplateElement.cs - Parse the property in
ElementParsers.cs - Add the property name to
KnownProperties.csfor the corresponding element type (YAML validation) - Add the property to
TemplateElement.CopyBaseProperties()(single source of truth inParsing/Ast/TemplateElement.cs) - If the property contains expressions (e.g.,
{{variable}}), also addProcessExpression()calls in the preprocessors - Write tests to verify the property survives the full rendering pipeline
- TextMeasurer delegate --
Func<TextElement, float, float, LayoutSize>?onLayoutEngine(element, fontSize, maxWidth), wired up bySkiaRendererfor content-based text sizing with wrap-aware height measurement - Content providers --
IContentProvider<TElement,TOutput>returns rendered content for QR, barcode, image elements - Resource loader chain --
IResourceLoaderwithCanHandle(),Load(),Priority-- chain of responsibility pattern - Flex-item properties -- declared per concrete element class, dispatched via
switchpattern matching (not on base class) - Template processing layers -- AST-level (
TemplateExpanderfortype: each/type: if) and inline (TemplateProcessorfor{{variable}})
All elements inherit these properties:
| Property | Type | Default | Description |
|---|---|---|---|
Rotate |
string | "none" |
Rotation of the element |
Background |
string? | null | Background color in hex or CSS gradient |
Opacity |
float | 1.0f | Element opacity (0.0-1.0), affects element and children |
BoxShadow |
string? | null | Box shadow: "offsetX offsetY blurRadius color" |
Padding |
string | "0" |
Padding inside (px, %, em, CSS shorthand) |
Margin |
string | "0" |
Margin outside (px, %, em, auto, CSS shorthand) |
Display |
Display | Flex |
Display mode (Flex, None) |
Grow |
float | 0 | Flex grow factor |
Shrink |
float | 1 | Flex shrink factor |
Basis |
string | "auto" |
Flex basis (px, %, em, auto) |
AlignSelf |
AlignSelf | Auto |
Self alignment override |
Order |
int | 0 | Display order |
Width |
string? | null | Width (px, %, em, auto) |
Height |
string? | null | Height (px, %, em, auto) |
MinWidth |
string? | null | Minimum width constraint |
MaxWidth |
string? | null | Maximum width constraint |
MinHeight |
string? | null | Minimum height constraint |
MaxHeight |
string? | null | Maximum height constraint |
Position |
Position | Static |
Positioning mode (Static, Relative, Absolute) |
Top |
string? | null | Top inset for positioned elements |
Right |
string? | null | Right inset for positioned elements |
Bottom |
string? | null | Bottom inset for positioned elements |
Left |
string? | null | Left inset for positioned elements |
AspectRatio |
float? | null | Width/height ratio; when one dimension is known, the other is computed |
Additional container-only properties:
| Property | Type | Default | Description |
|---|---|---|---|
Direction |
FlexDirection | Column |
Main axis direction |
Wrap |
FlexWrap | NoWrap |
Whether items wrap to new lines |
Gap |
string | "0" |
Gap shorthand (sets both row-gap and column-gap) |
ColumnGap |
string? | null | Gap between items along main axis |
RowGap |
string? | null | Gap between wrapped lines |
Justify |
JustifyContent | Start |
Main axis alignment |
Align |
AlignItems | Stretch |
Cross axis alignment |
AlignContent |
AlignContent | Start |
Alignment of wrapped lines (note: CSS default is Stretch) |
Overflow |
Overflow | Visible |
Content overflow behavior (Visible, Hidden) |
All enums in FlexRender.Layout.FlexEnums:
| Enum | Values | Description |
|---|---|---|
FlexDirection |
Row, Column, RowReverse, ColumnReverse | Main axis direction |
FlexWrap |
NoWrap, Wrap, WrapReverse | Line wrapping behavior |
JustifyContent |
Start, Center, End, SpaceBetween, SpaceAround, SpaceEvenly | Main axis alignment |
AlignItems |
Start, Center, End, Stretch, Baseline | Cross axis alignment |
AlignContent |
Start, Center, End, Stretch, SpaceBetween, SpaceAround, SpaceEvenly | Multi-line alignment |
AlignSelf |
Auto, Start, Center, End, Stretch, Baseline | Per-item alignment override |
Display |
Flex, None | Element visibility in layout |
Position |
Static, Relative, Absolute | CSS positioning mode |
Overflow |
Visible, Hidden | Content overflow handling |
MarginValue -- readonly record struct representing a single margin side:
MarginValue.Fixed(float px)-- fixed pixel valueMarginValue.Auto-- auto margin that consumes free spaceResolvedPixels-- resolved value (0 for unresolved auto)
MarginValues -- readonly record struct for all four sides:
Top,Right,Bottom,Left-- individualMarginValuesidesHasAuto-- whether any side is autoMainAxisAutoCount(bool isColumn)-- count of auto margins on main axis (0, 1, or 2)CrossAxisAutoCount(bool isColumn)-- count of auto margins on cross axis (0, 1, or 2)MarginValues.Zero-- all sides zero, no auto
PaddingParser.ParseMargin(string, float, float) -- parses CSS margin shorthand with auto support, returns MarginValues.
- type: each
array: items # path to array in data (required)
as: item # variable name (optional)
children:
- type: text
content: "{{item.name}}: {{item.price}}"Loop variables available inside each:
{{@index}}-- zero-based index{{@first}}-- true for first item{{@last}}-- true for last item
Supports 12 operators: truthy (no key), equals, notEquals, in, notIn, contains, greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual, hasItems, countEquals, countGreaterThan.
# Truthy check
- type: if
condition: isPremium
then:
- type: text
content: "Premium"
# Equality (strings, numbers, bool, arrays, null)
- type: if
condition: status
equals: "paid"
then:
- type: text
content: "Paid"
# In list
- type: if
condition: role
in: ["admin", "moderator"]
then:
- type: text
content: "Staff"
# Numeric comparison
- type: if
condition: total
greaterThan: 1000
then:
- type: text
content: "Large order"
# Array checks
- type: if
condition: items
hasItems: true
then:
- type: each
array: items
children:
- type: text
content: "{{item.name}}"
# Else-if chain
- type: if
condition: status
equals: "paid"
then:
- type: text
content: "Paid"
elseIf:
condition: status
equals: "pending"
then:
- type: text
content: "Pending"
else:
- type: text
content: "Unknown"