-
Notifications
You must be signed in to change notification settings - Fork 1
Components
Warning
Outdated: the legacy web component library documented below was EXCISED on 2026-07-31 — the write-once library (WriteOnceComponents, 28 components) is the only architecture. This page is kept for history.
Note
This catalog covers the legacy web component library (eQuantic.UI.Components). The framework is migrating to a write-once library (eQuantic.UI.Components.Shared) authored against the Photon vocabulary and realized on web AND native — 8 components migrated so far, mixing is safe in both directions. See Write-Once Components.
Complete reference of the eQuantic.UI component system — architecture, catalog, and production patterns.
HtmlNode is the basic unit of the Virtual DOM — a lightweight representation of a real DOM element.
-
Tag: HTML tag name (e.g.,
div,button) - Key: Unique identifier for reconciliation optimization
-
Attributes: Dictionary of HTML attributes (
class,id,style, etc.) - Events: C# delegates converted into event listeners
-
Children: List of child
HtmlNodes -
TextContent: Used for text nodes (
#text)
| Type | Description | Use Case |
|---|---|---|
StatelessComponent |
Functional components depending only on props. Override Build() to return the component tree. |
Visual components: Text, Heading, Badge
|
StatefulComponent |
Components with persistent internal state. SetState triggers re-render of the component and children. |
Interactive pages, forms, dashboards |
HtmlElement |
Low-level primitives mapping directly to HTML tags. |
Box, Flex, Grid, Container
|
InputComponent<TValue> |
Base for input components with value binding, events, and theme integration. |
TextInput, Select, Checkbox
|
Events follow the On{EventName} convention. The compiler removes the On prefix and converts to lowercase in TypeScript:
new Button {
Text = "Clear",
Variant = Variant.Secondary,
OnClick = HandleClear
}| Interface | Purpose | Documentation |
|---|---|---|
IRequireAssets |
Declare script/CSS dependencies | Asset Management |
IHandleMetadata |
Configure SEO metadata | Server Integration |
| Component | Description | Key Props |
|---|---|---|
Container |
Responsive max-width container |
MaxWidth, Fluid
|
Box |
Generic block container (div) |
Children |
Flex |
Flexbox container |
Direction, Justify, Align, Gap, Wrap
|
Row |
Horizontal flex container | Inherits from Flex
|
Column |
Vertical flex container | Inherits from Flex
|
Stack |
Stack layout with spacing |
Direction, Gap
|
Grid |
CSS Grid layout |
Columns, Gap
|
GridItem |
Grid child with placement |
ColSpan, RowSpan
|
new Grid { Columns = 3, Gap = "4", Children = {
new GridItem { ColSpan = 2, Content = new Text("Wide") },
new GridItem { Content = new Text("Narrow") }
}}| Component | Description | Key Props |
|---|---|---|
TextInput |
Text input field |
Type, Placeholder, Value, OnChange, Size, Disabled
|
TextArea |
Multi-line text input |
Rows, Value, OnChange, Disabled
|
Checkbox |
Checkbox with optional rich UI |
Checked, OnChange, IsNative
|
Radio |
Radio button |
Checked, OnChange, Name, Label
|
RadioGroup |
Radio button group |
Value, OnChange, Options, Direction
|
RadioOption |
Data class for radio options |
Value, Label, Disabled
|
Select |
Dropdown select |
Value, OnChange, Multiple, Options, IsNative
|
SelectOption |
Data class for select options |
Value, Label, Disabled
|
Switch |
Toggle switch (inherits Checkbox) |
Value, OnChange, Label
|
Slider |
Range slider |
Value, Min, Max, Step, OnChange
|
All inputs inherit from InputComponent<TValue>, which provides:
-
Value/OnChange/OnInputevent binding -
Disabled,ReadOnly,Requiredstates - Theme integration via
IInputTheme
All input components support both modes:
// Controlled — external state management, real-time validation
new TextInput {
Value = email,
OnChange = (value) => SetState(() => email = value)
}
// Uncontrolled — internal state, simple forms
new TextInput {
DefaultValue = "user@example.com",
Name = "email"
}When to use each:
- Controlled: Real-time validation, dependent fields, complex forms
- Uncontrolled: Simple forms, server-side validation, minimal state
| Component | Description | Key Props |
|---|---|---|
FormField |
Label + input + error wrapper |
Label, Error, Required, HelperText, For
|
InputGroup |
Input with prefix/suffix addons | Children |
// FormField with validation
new FormField {
Label = "Email Address",
Required = true,
Error = validationError,
HelperText = "We'll never share your email",
For = "email-input",
Children = {
new TextInput { Id = "email-input", Type = "email", Name = "email" }
}
}
// InputGroup with addons
new InputGroup {
Children = {
new InputAddon { Text = "https://" },
new TextInput { Placeholder = "example.com" },
new Button { Text = "Go" }
}
}| Component | Description | Key Props |
|---|---|---|
Badge |
Status/category badge |
Text, Variant
|
Avatar |
User avatar (image or initials) |
ImageUrl, AltText, Initials, Size
|
Heading |
H1-H6 heading |
Content, Level (1-6) |
Text |
Text span/paragraph |
Content, Variant, Paragraph
|
Icon |
Unified icon component |
Name (e.g., "lucide:search"), Size
|
CodeBlock |
Syntax-highlighted code |
Code, Language, Collapsible
|
List |
Ordered/unordered list |
Ordered, Unstyled, Children
|
ListItem |
List item |
Active, Disabled, OnClick
|
Table<T> |
Data table |
Data, Columns, Striped, OnRowClick
|
Renders syntax-highlighted code using Prism.js with automatic dark/light theme switching:
new CodeBlock("Console.WriteLine(\"Hello\");", "csharp") { Collapsible = true }Features:
- Prism.js loaded automatically via IRequireAssets
- Copy to clipboard button
- Collapsible/expandable code blocks
- Dark/light theme auto-switches with the page theme
new Table<User> {
Data = users,
Columns = new[] {
new TableColumn { Header = "Name", Field = "Name" },
new TableColumn { Header = "Email", Field = "Email", Sortable = true }
}
}| Component | Description | Key Props |
|---|---|---|
Card |
Card container |
Variant, Shadow
|
CardHeader |
Card header section | Children |
CardTitle |
Card title | Text |
CardDescription |
Card description | Text |
CardBody |
Card body content | Children |
CardFooter |
Card footer | Children |
new Card { Variant = CardVariant.Elevated, Shadow = Shadow.Large, Children = {
new CardHeader { Children = {
new CardTitle { Text = "Q1 2026 Roadmap" },
new CardDescription { Text = "Key initiatives" }
}},
new CardBody { Children = {
new Text("Launch mobile app"),
new Text("Implement AI features")
}},
new CardFooter { Children = {
new Button { Text = "View Details", Variant = Variant.Outline },
new Button { Text = "Share", Variant = Variant.Ghost }
}}
}}CardVariant.Default // Filled card with background
CardVariant.Outline // Border only, transparent background
CardVariant.Elevated // Prominent shadow for emphasis
CardVariant.Subtle // Minimal visual weight
CardVariant.Ghost // No background or border| Component | Description | Key Props |
|---|---|---|
Alert |
Alert message | Variant |
AlertTitle |
Alert title | Text |
AlertDescription |
Alert description | Text |
Toast |
Toast notification |
Variant, DelayMs, Title, Message
|
ToastContainer |
Toast positioning container |
Position (TopRight, BottomLeft, etc.) |
Spinner |
Loading indicator |
Variant (Border/Grow), Size (Small/Normal) |
new Alert { Variant = Variant.Warning, Children = {
new AlertTitle { Text = "Warning" },
new AlertDescription { Text = "This action cannot be undone." }
}}| Component | Description | Key Props |
|---|---|---|
Tabs |
Tab navigation |
DefaultValue, Value, OnValueChange
|
TabsList |
Tab header list | Children |
TabsTrigger |
Tab trigger button |
Value, Text
|
TabsContent |
Tab content panel |
Value, Children
|
Accordion |
Collapsible sections |
Type (Single/Multiple), Collapsible
|
AccordionItem |
Accordion section |
Value, Children
|
AccordionTrigger |
Accordion trigger button | Text |
AccordionContent |
Accordion content panel | Children |
Breadcrumb |
Breadcrumb navigation |
Items, Separator
|
Navbar |
Top navigation bar |
Brand, Items, Dark, Fixed
|
Sidebar |
Side navigation panel |
Items, Width, Collapsed
|
NavigationMenu |
Complex nav menu |
Children (compound pattern) |
new Tabs { DefaultValue = "tab1", Children = {
new TabsList { Children = {
new TabsTrigger { Value = "tab1", Text = "General" },
new TabsTrigger { Value = "tab2", Text = "Advanced" }
}},
new TabsContent { Value = "tab1", Children = { ... } },
new TabsContent { Value = "tab2", Children = { ... } }
}}| Component | Description | Key Props |
|---|---|---|
Modal |
Modal dialog |
IsOpen, Title, OnClose
|
Drawer |
Slide-out panel |
IsOpen, Side, OnClose
|
DrawerContent |
Drawer body |
Width, Children
|
DrawerTrigger |
Drawer open trigger | Children |
ContextMenu |
Right-click menu |
Children (compound pattern) |
ContextMenuItem |
Menu item |
Disabled, Shortcut
|
new Drawer { Side = DrawerSide.Right, IsOpen = isOpen, Children = {
new DrawerContent { Children = {
new DrawerTitle { Text = "Settings" },
// content...
}}
}}| Component | Description |
|---|---|
DynamicElement |
Generic HTML element (any tag, attributes, events) |
NullComponent |
Empty/null component (renders nothing) |
Link |
Anchor tag with Href, Target
|
Button |
Button with Variant, Size, Loading, Disabled
|
All components inherit from IComponent and share:
| Prop | Type | Description |
|---|---|---|
Id |
string? |
HTML id attribute |
ClassName |
string? |
CSS class(es) |
Style |
HtmlStyle? |
Inline styles |
DataAttributes |
Dictionary<string, string>? |
data-* attributes |
AriaAttributes |
Dictionary<string, string>? |
aria-* attributes |
Children |
List<IComponent> |
Child components |
new Button {
Text = "Submit",
DataAttributes = new Dictionary<string, string> {
["testid"] = "submit-button",
["analytics"] = "checkout-submit"
},
AriaAttributes = new Dictionary<string, string> {
["label"] = "Submit order",
["describedby"] = "submit-help"
}
}
// Renders: <button data-testid="submit-button" aria-label="Submit order">Submit</button>Most components support theme-driven styling:
-
Variant:
Primary,Secondary,Destructive,Outline,Ghost,Link,Success,Warning,Info -
Size:
Small,Medium,Large,XLarge
Themes are provided by IAppTheme (default eQuantic theme or Tailwind via AddTailwind()).
StyleBuilder is a utility inspired by CVA (Class Variance Authority) for managing conditional CSS classes:
["class"] = StyleBuilder.Create(theme?.Base)
.Add(theme?.GetVariant(Variant))
.Add(theme?.GetSize(Size))
.Add(ClassName)
.Build()Component themes inject an IColorTheme interface that maps semantic colors to concrete classes, facilitating global color palette customization.
Inputs support state-based variants for validation feedback:
-
Success- Valid/successful input -
Warning- Warning state -
Destructive- Error state -
Ghost- Minimal style
public class MyCardTheme : ICardTheme
{
public string Container => "my-custom-card";
public string Title => "my-custom-title";
public string GetVariant(CardVariant variant) => variant switch
{
CardVariant.Outline => "my-outline-variant",
_ => ""
};
}new Button {
Text = "Submit Order",
Loading = isSubmitting, // Automatic spinner + disabled state
OnClick = HandleSubmit
}Features:
- Automatic spinner rendering
- Button disabled during loading
-
data-loading="true"attribute for CSS targeting - Accessible with
aria-hidden="true"on spinner
new FormField {
Label = "Email Address",
Required = true,
Error = validationError,
HelperText = "We'll never share your email",
Children = {
new TextInput { Type = "email", Name = "email" }
}
}Features:
- Label with automatic required indicator (*)
- Error messages styled with
role="alert"for accessibility - Helper text displayed when no errors present
- Automatic
.eq-errorclass on wrapper
For complex UIs, use the compound pattern for semantic structure:
// Recommended — semantic and flexible
new Card {
Variant = CardVariant.Elevated,
Children = {
new CardHeader { Children = { new CardTitle { Text = "Title" } } },
new CardBody { Children = { new Text("Content") } },
new CardFooter { Children = { new Button { Text = "Save" } } }
}
}- Use compound components for complex UIs (Card, Tabs, Accordion)
- Use controlled mode for real-time validation, uncontrolled for simple forms
- Wrap inputs in FormField for consistent validation UX
- Add loading states to all async action buttons
- Add Data/ARIA attributes for testing and accessibility
| Feature | eQuantic.UI | shadcn/ui | Advantage |
|---|---|---|---|
| Compound Components | Full support | Full support | Parity |
| Variants | 5+ per component | 5+ per component | Parity |
| Controlled/Uncontrolled | TextInput, Select, Checkbox | Full support | Parity |
| Loading States | Button + inputs | Button + inputs | Parity |
| Error States | FormField component | Form component | Parity |
| Input Groups | Full support | Full support | Parity |
| Data/ARIA Attributes | Native support | Native support | Parity |
| Documentation | XML (IntelliSense) | JSDoc | eQuantic |
| Type Safety | C# Compiler | TypeScript | eQuantic |
| Theming System | DI + Fallback | CSS vars | eQuantic |
| Performance | Compile-time | Runtime | eQuantic |
- Asset Management - Component asset dependencies
- Styling - Theme system and styling patterns
- Server Integration - SSR, SEO, and server configuration
- Architecture - Overall framework architecture
A list that only BUILDS the rows you can see: give it a count, a fixed per-item extent and a builder by index, and it materializes the visible window plus an overscan margin — the rest of the list is two spacers, so layout (and the scrollbar) see the true content height while ten thousand rows cost what a screenful costs. Write-once: the same C# runs on Photon and on the web.
new ListView(count: 10_000, itemExtent: 44, itemBuilder: i => RowFor(i))
{
Width = SizeValue.Fill,
Height = SizeValue.Fill, // the list is a WINDOW — give it one
}The window converges rather than blocks: the first frame builds against a viewport guess, the
realized frame reports the true viewport and offset through the ScrollView's out-channels
(OnScrolled / OnViewportChanged — wired on both targets), and the next frame builds the
corrected window. Pixels scroll without a rebuild; state only changes when a row crosses the
overscan margin. v1 fence: vertical, fixed ItemExtent (variable extents need incremental
measurement and join later).
Layout rules that make windows work everywhere (SDK invariants, not app chores):
- The app shell's
#appframe has EXACT viewport height (height: 100dvh, grid, childrenmin-height: 0): an APP page (rootHeight = Fill) resolves to exactly one viewport and scrolls internally; a DOCUMENT page (auto-height root) overflows the frame and the body scrolls as it always did. - A
ScrollViewwithout an explicitHeightdefaults toheight: 100%on web — a scroll view IS the window its parent gives it, never its content (native parity: the realizer hands it layout bounds and clips always). - Hydration adopts
data-eq-*framework markers from the client tree — SSR cannot know client-side identities, and the after-pass sweeps find their elements by them.
An Excel-usability spreadsheet is landing as a write-once component, built the way the code editor
was: the controller carries everything, the pixels come later. Slice 1 is the model, shared
verbatim across targets (eQuantic.UI.Primitives/Sheet):
-
SheetDocument— sparse cells (empty is absent), per-row/column sizes, logical extent. -
CellRef/SheetRange— A1 addressing; the anchor/focus two-ended selection, in 2D. Cells key by a packed int (row × 16384 + col, Excel's own column ceiling) because a record struct is a fine C# dictionary key and a useless JS Map key. -
SheetController— Excel's semantics as methods: arrows/Shift extend, Ctrl+arrow data-edge jumps, Tab/Enter walking (and wrapping) inside a selection while the rectangle stands still (active cell ≠ selection focus, deliberately), header band selections, cell edits, row/column insert/delete/resize with sparse inverse-based undo/redo, and TSV copy/paste that speaks Excel's quoting — interop with real Excel/Sheets is the clipboard dialect, so it is in the model, tested, from day one. - Fences v1, stated: no formula engine, no per-cell formatting, no merges, no frozen panes.
15 xunit tests pin the contract; the transpiled twin runs the same moves under vitest. Next
slices: the SheetSurface node + virtualized grid render, in-cell editing, clipboard/Studio/a11y.