# NeoUI -- Usage Patterns & Best Practices Practical patterns for the most common scenarios when building Blazor apps with NeoUI. --- ## Forms ### Basic Validated Form ```razor @using System.ComponentModel.DataAnnotations Email Password @code { private LoginModel _model = new(); private async Task HandleSubmit() { // validated, safe to submit } private class LoginModel { [Required, EmailAddress] public string Email { get; set; } = ""; [Required, MinLength(8)] public string Password { get; set; } = ""; } } ``` ### Select / Combobox Binding ```razor @* Native select -- simple, always works *@ @* Combobox -- searchable *@ @foreach (var country in _countries) { @country.Name } ``` ### Multi-Select ```razor React Vue Angular Svelte @code { private IEnumerable? _tags; } ``` ### Date & Time Pickers ```razor @* Date only *@ @* Date range *@ @* Time picker *@ @code { private DateOnly? _date; private DateOnly? _start; private DateOnly? _end; private TimeOnly? _time; } ``` ### File Upload ```razor

Drag files here or browse

PDF, DOCX up to 10 MB

``` --- ## Data Binding Patterns ### Two-Way Binding (@bind-Value) ```razor @* Standard two-way binding *@ Small Medium Large @code { private string _name = ""; private bool _enabled = true; private double _volume = 50; private bool _agreed = false; private string _size = "md"; } ``` ### Controlled Open/Close State ```razor @* Controlled dialog *@ Edit @code { private bool _open = false; private void Save() { /* ... */ _open = false; } } ``` ### Tab State ```razor Overview Details History @code { private string? _activeTab; // Switch programmatically: _activeTab = "details"; } ``` --- ## Toasts / Notifications ### Service Injection ```razor @inject IToastService Toast @code { private void ShowSuccess() => Toast.Show("Changes saved", "Your changes have been saved successfully.", ToastVariant.Default); private void ShowError() => Toast.Show("Error", "Could not delete item. Please try again.", ToastVariant.Destructive); } ``` ### Setup (once in MainLayout.razor or App.razor) ```razor ``` --- ## Dialogs -- Programmatic (DialogService) ```razor @inject IDialogService DialogService @code { private async Task ConfirmDelete() { var result = await DialogService.ShowAsync( new DialogParameters { ["ItemName"] = "Document" }); if (result.Confirmed) await DeleteItemAsync(); } } ``` ```razor @* ConfirmDialog.razor *@ Delete @ItemName? This action cannot be undone. @code { [Parameter] public string ItemName { get; set; } = ""; [CascadingParameter] public DialogInstance Dialog { get; set; } = null!; private async Task Confirm() => await Dialog.CloseAsync(new DialogResult { Confirmed = true }); } ``` --- ## Theming ### Runtime Theme Switching ```razor @inject ThemeService ThemeService @* Built-in UI -- picks base + accent *@ @* Built-in dark mode toggle *@ @* Programmatic *@ ``` ### CSS Variable Overrides ```css /* In your app.css -- override any variable per-component or globally */ :root { --radius: 0.25rem; /* sharper corners */ --primary: oklch(0.6 0.2 250); /* custom brand color */ } /* Scoped override */ .my-card { --card: oklch(0.98 0 0); } ``` ### Conditional Dark Mode Classes ```razor @* Tailwind dark: variant *@
Content
``` --- ## Sidebar Layout Pattern ```razor @* MainLayout.razor *@ My App Main Home Settings John Doe
...
@Body
``` --- ## Data Grid Patterns ### Basic Grid ```razor @order.Status ``` ### Server-Side Grid ```razor @code { private async Task> FetchPage(DataGridDataRequest req) { var (items, total) = await _api.GetProductsAsync( page: req.StartRow / req.PageSize, pageSize: req.PageSize, sortField: req.SortModel.FirstOrDefault()?.ColId, sortDir: req.SortModel.FirstOrDefault()?.Sort, filter: req.FilterModel.GetValueOrDefault("Name")?.Filter); return new DataGridDataResponse { Items = items, TotalCount = total }; } } ``` --- ## Render Mode Gotchas ### Interactive Components Need Interactive Render Mode Tabs, NavigationMenu, Combobox, DataGrid -- all require interactive rendering. ```razor @* App.razor -- set global interactive render mode *@ ``` ```razor @* Or per-component in a Static SSR page *@ ... ``` ### InteractiveAuto Requires Registration in Both Projects ```csharp // Server project Program.cs builder.Services.AddRazorComponents() .AddInteractiveServerComponents() .AddInteractiveWebAssemblyComponents(); // Both Server AND WASM Client Program.cs builder.Services.AddNeoUIPrimitives(); builder.Services.AddNeoUIComponents(); builder.Services.AddScoped(); ``` ### SidebarProvider with Static Rendering ```razor @* Set StaticRendering="true" when parent renders statically *@ ... ``` --- ## Keyboard Shortcuts ```razor @inject IKeyboardShortcutService KeyboardShortcuts @code { protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { await KeyboardShortcuts.RegisterAsync("Ctrl+K", OpenCommandPalette); await KeyboardShortcuts.RegisterAsync("Escape", CloseAll); } } private void OpenCommandPalette() { /* ... */ } private void CloseAll() { /* ... */ } } ``` --- ## Loading States Pattern ```razor @if (_loading) {
} else if (_error is not null) { Failed to load @_error } else if (!_items.Any()) { No items yet Create your first item to get started. } else { @foreach (var item in _items) { } } ```