-
Notifications
You must be signed in to change notification settings - Fork 1
Styling
eQuantic.UI provides a Flutter-inspired developer experience (DX) while leveraging the full power of each rendering target. The styling architecture is built on three pillars:
- Abstraction: Components author what to style — typed C# values, never CSS strings.
- One semantics, two targets: the same style vocabulary drives dp values on native (Photon) and CSS on the web.
- Performance: build-time class generation and deduplication — no CSS-in-JS runtime overhead.
Component styling is authored entirely in typed C# (BoxStyle, StyleDiff, design tokens) and
lowered by the atomic style engine: every regular declaration becomes ONE deduplicated atomic
class, byte-identical between SSR (C#) and hydration (TS); theme colors reference
var(--eq-color-*) custom properties. Hover/focus states, window-size-class adaptivity, sticky
positioning and transforms are all part of the vocabulary — zero JavaScript involved.
The full engine — laws, pseudo-states, size classes, the generated stylesheet — is documented in
DesignSystem. The abstract vocabulary itself (Box, Row/Column, Grid,
Stack, …) is documented in Write-Once Components.
Themes are typed C# too — colors as light/dark ColorToken pairs, type roles, shape scale,
elevation — selected in one line and bridged SSR→client:
builder.Services.AddUI(options =>
{
options.ScanAssembly(typeof(Program).Assembly)
.UseTheme(PhotonTheme.Instance); // or MaterialTheme.FromSeed(color)
});Two implementations ship: PhotonTheme (the default) and MaterialTheme (real Material 3,
including dynamic color from a seed). Swap the theme and every component rebrands — server and
client, web and native. Runtime light/dark switching is a service (IThemeController); see
DesignSystem.
The low-level web layer (eQuantic.UI.Core) mirrors the DOM 1:1. Every HtmlElement exposes the
attributes the browser has:
public abstract class HtmlElement : IComponent {
/// <summary>Raw CSS classes (space-separated).</summary>
public string? ClassName { get; set; }
/// <summary>Inline styles for dynamic values (e.g., coordinates, colors from DB).</summary>
public HtmlStyle? Style { get; set; }
}This contract means the web layer does not enforce any CSS framework — it renders HTML
attributes. DynamicElement goes further and renders any tag with any attributes. Standard CSS
classes from any stylesheet work natively:
new DynamicElement("aside") { ClassName = "my-sidebar" }For app-authored markup that wants utility classes, the eQuantic.UI.Tailwind package generates
the CSS at build time through the embedded Bun (bun x @tailwindcss/cli) — zero Node.js, zero
npm, zero manual targets.
<PackageReference Include="eQuantic.UI.Tailwind" Version="*" />src/styles.css (the default input path):
@import "tailwindcss";
@theme {
--font-family-sans: "Inter", "sans-serif";
--color-primary: #3b82f6;
}The build targets default to src/styles.css → wwwroot/css/app.css. Override in the .csproj:
<PropertyGroup>
<TailwindInput>wwwroot/css/styles.css</TailwindInput>
<TailwindOutput>wwwroot/css/app.css</TailwindOutput>
</PropertyGroup>builder.Services.AddUI(options =>
{
options.UseTailwind(); // injects <link href="/css/app.css"> (cache-busted per build)
});Tip
Zero External Dependencies: the Tailwind CLI runs through the Bun bundled inside the NuGet
packages. No Node.js, npm, or global installations required — just dotnet build.
For Tailwind's class-based dark mode, EnableTailwindDarkMode() injects a lightweight boot script
into the <head> that applies the .dark class on <html> before first paint (no flash of
unstyled content), reading localStorage and falling back to the OS preference:
options.ConfigureHtmlShell(shell => shell.EnableTailwindDarkMode());new DynamicElement("div") {
ClassName = "bg-white dark:bg-zinc-950 border-zinc-200 dark:border-zinc-800"
}(Write-once components need none of this: their palette is authored as light/dark token pairs and
the theme switch flips one color-scheme declaration — see DesignSystem.)
The package ships typed helpers (TW, TailwindClass, ClassBuilder) whose calls are evaluated
at build time into plain string literals — zero runtime cost. See
Compile-Time Evaluation for the recognized patterns and how the
evaluation works.
| Package | Purpose |
|---|---|
| eQuantic.UI.Primitives | Typed styles, design tokens, the abstract vocabulary — the styling source of truth. |
| eQuantic.UI.Core | The DOM-mirror layer: HtmlElement, ClassName, HtmlStyle, DynamicElement. |
| eQuantic.UI.Web | The web realizer + atomic style engine (StyleAtomizer) + generated stylesheet. |
| eQuantic.UI.Tailwind | Build-time utility CSS generation (embedded Bun) + TW compile-time helpers. |