Component library

Dev-only page (404s outside Development) for seeing this template's Tag Helpers live. Source: src/Train.Web/TagHelpers.

Buttons

Built from this template's semantic tokens (app.css's @theme block — bg-brand, text-heading, rounded-base, ...), not raw Tailwind palette colors, so re-theming is "change the token value" rather than "grep every component".

<train-button>Default</train-button>
<train-button variant="secondary">Secondary</train-button>
<train-button variant="danger" size="sm">Small danger</train-button>
<train-button variant="primary" disabled>Disabled</train-button>

variant defaults to "primary"; size defaults to "md".

Form fields

Submit empty to see <train-field> report validation errors the same way a real page's form would.

At least 8 characters.

<train-field asp-for="Input.Email" label="Email address" type="email" placeholder="you@example.com" />
<train-field asp-for="Input.Password" label="Password" type="password" help="At least 8 characters." />
<train-field asp-for="Input.Bio" label="Bio" type="textarea" placeholder="Tell us about yourself" />
<train-field asp-for="Input.FloatingEmail" label="Email address" type="email" variant="floating" />
<train-field asp-for="Input.Role" label="Role" type="select" items="Html.GetEnumSelectList<SomeEnum>()" />
<train-field asp-for="Input.AcceptTerms" type="checkbox" label="I accept the terms" />

type defaults to "text"; variant defaults to "stacked" (label above the input) and is ignored for type="checkbox". items is required for type="select".

Card

This block is itself a <train-card> — the wrapper you're reading this text inside of. It takes arbitrary child content, with an optional title.

Tabs

Click a tab, or focus one and use Left/Right/Home/End — everything's server-rendered up front (TagHelpers/TabsTagHelper.cs), wwwroot/js/tabs.js just toggles visibility.

Overview panel content. Each <train-tab> renders nothing itself — TabsTagHelper collects every child's label and content and builds the tablist/panel pair from that.

<train-tabs>
    <train-tab label="Overview">...</train-tab>
    <train-tab label="Details">...</train-tab>
    <train-tab label="Settings">...</train-tab>
</train-tabs>

The first <train-tab> starts active. <train-tab> is only valid directly inside <train-tabs>.

Banner

An announcement strip with an icon, message, optional CTA link, and a close button. wwwroot/js/banner.js removes it from the DOM on close — dismissal isn't persisted, so it's back on the next page load. Click the × to try it (reload the page to bring it back).

New: DataTable now supports infinite scroll for large result sets. See changelog
<train-banner cta-text="See changelog" cta-href="/changelog">
    New: DataTable now supports infinite scroll for large result sets.
</train-banner>

cta-text/cta-href are optional — omit either to skip the CTA link. dismissible defaults to true; set dismissible="false" to drop the close button.

Image upload

Pick a file and it uploads immediately — wwwroot/js/image-upload.js posts it to Admin/Uploads/Image.cshtml.cs, which stores it (locally in development, Azure Blob Storage in production once AzureStorage:ConnectionString is configured) and returns its URL. The bound field just ends up holding that URL, same as any other <train-field>.

<train-image-upload asp-for="Input.ImageUrl" label="Photo" />

JPEG, PNG, GIF, and WebP only, up to 5 MB. Rejected uploads leave the current value untouched.

No file chosen

<train-image-upload asp-for="Input.ImageUrl" label="Photo" variant="floating" />

Panel

A card with an Edit/Add/Delete icon toolbar (wwwroot/js/panel.js toggles which icons and which half of each <train-panel-field> are visible — Save is a plain type="submit", so saving is a normal postback like every other form here). Built for Admin/UtilityBar/Edit's Brand tab first; the shell takes arbitrary body content, so later panels aren't limited to label/value grids like this one.

Brand

Title

Train

Subtitle

Web Template

Logo

Not set

<train-panel title="Brand" columns="3" editable="true">
    <train-panel-field asp-for="Input.PanelTitle" label="Title" />
    <train-panel-field asp-for="Input.PanelSubtitle" label="Subtitle" />
    <train-panel-field asp-for="Input.PanelLogoUrl" label="Logo" type="image" />
</train-panel>

addable/deletable add their own icons next to Edit — this demo only sets editable since a fixed Brand-shaped panel has nothing to add or delete.

Carousel

Prev/next buttons, dot indicators, and optional autoplay (paused on hover/focus) — wwwroot/js/carousel.js just toggles visibility, everything's server-rendered up front (TagHelpers/CarouselTagHelper.cs).

Slide 1
<train-carousel autoplay-interval="4000">
    <train-carousel-slide>...</train-carousel-slide>
    <train-carousel-slide>...</train-carousel-slide>
    <train-carousel-slide>...</train-carousel-slide>
</train-carousel>

autoplay-interval (ms) is optional — omit it to disable autoplay. Prev/next and dots only render when there's more than one slide.

DataTable

Two modes: Admin → Navigation is a small, fully-rendered table with client-side sort/search/filter. DataTable at scale is the same component in paged mode — 250,000 rows, server-side sort/search/filter, and infinite scroll with prefetch — for when a table has too many rows to ever render at once.