Built-in utility variants​
Import the variants from Akbura.Markup and place a markup extension before
an AKCSS utility:
using Akbura.Markup;
using Akbura.Styles.akcss;
<Button p-2
${hover}:p-3
${focusVisible}:opacity-100 />
<TextBox opacity-75
${focus}:opacity-100
${invalid}:border-red-500 />
<Border bg-white
${dark}:bg-slate-900
${light}:bg-slate-50 />
Every built-in variant uses
UnprefixedUtilityPrecedence.Above and installs the winning property
operation at BindingPriority.StyleTrigger. When a condition becomes false,
Akbura disposes only that contribution and Avalonia reveals the next winning
value.
Names that contain a hyphen in Tailwind use camel case in Akbura because
markup-extension type names are C# identifiers. For example,
focus-within becomes ${focusWithin}, max-md becomes
${maxMd}, and out-of-range becomes ${outOfRange}.
Interaction and theme​
| Variant | Active condition | Conflict group | Order |
|---|---|---|---|
${focusWithin} |
The target or a descendant has keyboard focus | Interaction | 5 |
${hover} |
The target's IsPointerOver is true |
Interaction | 10 |
${focus} |
The target's own IsFocused is true |
Interaction | 20 |
${focusVisible} |
Avalonia exposes :focus-visible on the target |
Interaction | 30 |
${active} |
A supported control exposes :pressed |
Interaction | 40 |
${light} |
The effective theme inherits ThemeVariant.Light |
Color scheme | 10 |
${dark} |
The effective theme inherits ThemeVariant.Dark |
Color scheme | 20 |
${focus} is intentionally different from ${focusWithin}: focusing a
child activates only the latter. ${focusVisible} follows Avalonia's input
navigation state rather than treating every focused control as keyboard
focused.
Theme variants observe the target's effective ActualThemeVariant. They
react to application, window, and inherited ThemeVariantScope changes. A
custom theme variant also matches the light or dark variant it inherits.
Tailwind has a built-in dark variant but normally uses unprefixed utilities
for light mode. Explicit ${light} is an Akbura convenience.
Control state​
| Variant | Avalonia condition | Supported target |
|---|---|---|
${enabled} |
IsEffectivelyEnabled == true |
Any InputElement |
${disabled} |
IsEffectivelyEnabled == false |
Any InputElement |
${visited} |
Pseudo-class :visited |
Controls that publish it, such as links |
${open} |
:open, :expanded, :dropdownopen, or :flyout-open |
Disclosure, menu, drop-down, and flyout controls |
${checked} |
Pseudo-class :checked |
Toggle controls |
${indeterminate} |
Pseudo-class :indeterminate |
Three-state toggle controls |
${selected} |
Pseudo-class :selected |
Selecting item containers |
${optional} |
AutomationProperties.IsRequiredForForm == false |
Any StyledElement |
${required} |
AutomationProperties.IsRequiredForForm == true |
Any StyledElement |
${valid} |
DataValidationErrors.HasErrors == false |
Any StyledElement |
${invalid} |
DataValidationErrors.HasErrors == true |
Any StyledElement |
${inRange} |
Non-null Value is between Minimum and Maximum, inclusive |
NumericUpDown |
${outOfRange} |
Non-null Value is outside Minimum and Maximum |
NumericUpDown |
${readOnly} |
IsReadOnly == true |
TextBox, NumericUpDown |
${placeholderShown} |
Text is empty and PlaceholderText is non-empty |
TextBox |
${default} |
IsDefault == true |
Button |
Related opposite states share a conflict group, so the applicable state wins
deterministically. Inherited disabling is respected because ${disabled}
uses IsEffectivelyEnabled, not only the target's local IsEnabled.
These are Avalonia mappings, not an HTML validation engine. In particular,
required state comes from automation metadata, valid state comes from
Avalonia data-validation errors, and ${default} means Avalonia's default
button. ${selected} is an Akbura convenience for item containers.
Pseudo-class-backed variants activate only on controls that publish the corresponding Avalonia pseudo-class.
Logical-tree structure​
| Variant | Active condition |
|---|---|
${first} |
First logical child |
${last} |
Last logical child |
${only} |
Only logical child |
${odd} |
Odd one-based logical-child position |
${even} |
Even one-based logical-child position |
${firstOfType} |
First sibling with the same StyleKey |
${lastOfType} |
Last sibling with the same StyleKey |
${onlyOfType} |
Only sibling with the same StyleKey |
${empty} |
No logical children |
The conditions update after insertions, removals, or reparenting. They use
Avalonia's logical tree rather than CSS's DOM tree, and “of type” compares the
effective Avalonia StyleKey.
The parameterized variants model CSS's an+b positions:
<Border ${nth Step=2, Offset=1}:bg-slate-100 />
<Border ${nthLast Step=0, Offset=3}:font-bold />
<Border ${nthOfType Step=2, Offset=0}:opacity-75 />
<Border ${nthLastOfType Step=-1, Offset=3}:p-2 />
Step=0 matches the single one-based position in Offset. The default is
Step=0, Offset=1.
Direction, viewport, and platform​
| Variant | Active condition |
|---|---|
${ltr} |
Effective FlowDirection.LeftToRight |
${rtl} |
Effective FlowDirection.RightToLeft |
${portrait} |
Top-level client height is at least its width |
${landscape} |
Top-level client width is greater than its height |
${contrastMore} |
Platform contrast preference is High |
Direction follows Avalonia's inherited flow direction. Orientation and
contrast observe the target's current TopLevel; if no top level or platform
settings are available, the extension does not produce a candidate.
Responsive variants​
The minimum breakpoints are mobile-first. Maximum breakpoints use an exclusive upper bound:
| Minimum | Width | Maximum | Width |
|---|---|---|---|
${sm} |
>= 640 |
${maxSm} |
< 640 |
${md} |
>= 768 |
${maxMd} |
< 768 |
${lg} |
>= 1024 |
${maxLg} |
< 1024 |
${xl} |
>= 1280 |
${maxXl} |
< 1280 |
${xxl} |
>= 1536 |
${maxXxl} |
< 1536 |
${xxl} and ${maxXxl} correspond to Tailwind's 2xl names because
a C# type cannot begin with a digit. All fixed breakpoints share one conflict
group. The largest active minimum breakpoint wins; among simultaneous maximum
conditions, the narrowest active range wins.
Arbitrary viewport thresholds use a named width in device-independent pixels:
<Border ${min Width=900}:p-6 />
<Border ${max Width=900}:p-2 />
${min} uses >= Width; ${max} uses < Width. They intentionally
use source order rather than joining the fixed-breakpoint scale.
Current limits​
Akbura currently accepts exactly one prefix per utility attribute. Native
Tailwind stacking such as dark:md:hover:... is not yet parsed; write
separate single-prefix attributes or a custom condition extension.
Variants that would change the utility target or require a CSS selector model
are not represented by a misleading approximation. This includes
pseudo-elements, direct/all-child selectors, group-*, peer-*,
has-*, in-*, not-*, arbitrary selector/data/ARIA variants,
target, and details-content.
The current Avalonia platform contract also has no portable reactive signal
for browser autofill, user-valid interaction history, reduced motion,
contrast-less, forced or inverted colors, pointer capability, scripting,
print, CSS @supports, or starting-style. Tailwind container queries need a
container-selection model and are therefore not treated as viewport queries.
See the Tailwind variant reference and responsive design documentation for the CSS originals.