Release v1.2.27
Ivy Framework Weekly Notes - Week of 2026-03-26
Note
We usually release on Fridays every week. Sign up on https://ivy.app/ to get release notes directly to your inbox.
New Features
DataTable Footer Aggregates
Display aggregate calculations (sum, average, count, etc.) directly in your DataTable footers. The new .Footer() method lets you add calculated summaries at the bottom of columns:
var invoiceLines = GetInvoiceLines();
invoiceLines.ToDataTable()
.Header(x => x.Product, "Product / Service")
.Header(x => x.Qty, "Quantity")
.Footer(x => x.Qty, "Total", values => values.Sum())
.Header(x => x.UnitPrice, "Unit Price")
.Footer(x => x.UnitPrice, "Avg", values => values.Average())
.Header(x => x.Amount, "Amount")
.Footer(x => x.Amount, "Total", values => values.Sum())
.Height(Size.Units(80))
New Skeleton Loading Variants
Added three new Skeleton placeholder methods.
// Show a text skeleton with multiple lines
Skeleton.Text(lines: 3) // Default is 3 lines, last line is shorter
// Show a data table skeleton with header and rows
Skeleton.DataTable(rows: 5) // Default is 5 rows
// Show a feed/list skeleton with avatar and content
Skeleton.Feed(items: 3) // Default is 3 items, includes avatar + text layoutProgrammatic Sheet Closing
The Sheet widget can now be closed programmatically from within its content using a close callback. WithSheet supports an overload where the content factory receives a close action, enabling forms and actions within sheets to automatically close after completion:
var client = UseService<IClientProvider>();
return new Button("Add Item").WithSheet(
close => {
var itemName = UseState("");
async ValueTask HandleSubmit()
{
await SaveItem(itemName.Value);
client.Toast($"Item '{itemName.Value}' added!").Success();
close(); // Close the sheet after successful submission
}
return new FooterLayout(
Layout.Horizontal().Gap(2)
| new Button("Submit").OnClick(_ => HandleSubmit())
| new Button("Cancel").Variant(ButtonVariant.Outline)
.OnClick(_ => close()),
new Card(
new TextInput("Item Name").Bind(itemName)
).Title("Item Details")
);
},
title: "Add New Item",
description: "Fill out the form below"
);Toolbar Widget for Action Buttons and Controls
A new Toolbar widget provides a way to organize action buttons, editor controls, and floating action bars.
var client = UseService<IClientProvider>();
new Toolbar()
| new MenuItem(Label: "Save", Icon: Icons.Save, Tag: "save")
.OnSelect(_ => client.Toast("Saved!"))
| new MenuItem(Label: "Undo", Icon: Icons.Undo, Tag: "undo")
.OnSelect(_ => client.Toast("Undo"))
| MenuItem.Separator()
| MenuItem.Default(Icons.ZoomIn, tag: "zoom-in")
.Tooltip("Zoom In")
.OnSelect(_ => client.Toast("Zoom In"))
Semantic Colors for MenuItem
DropDownMenu items (including MenuItem) now support semantic color styles beyond the basic Default and Destructive options.
DropDownMenu.Default()
.Trigger(new Button("Actions"))
.Items(
MenuItem.Default("Save").Primary()
| MenuItem.Default("Draft").Secondary()
| MenuItem.Default("Publish").Success()
| MenuItem.Default("Review").Warning()
| MenuItem.Default("Info").Info()
| MenuItem.Default("Delete").Destructive()
)
SelectInput Bulk Selection Actions
SelectInput multi-select variants now support bulk actions with the .ShowActions() modifier. This adds "Select All" and "Clear All" buttons in the dropdown footer, making it easy to manage large lists:
var languages = UseState<string[]>([]);
var options = new[] { "C#", "Java", "Python", "JavaScript", "Go", "Rust" }.ToOptions();
languages.ToSelectInput(options)
.Variant(SelectInputVariant.Select)
.ShowActions()
.Searchable(true)
.Placeholder("Select languages...")Automatic Enum Formatting in SelectInput
Enums now display with readable, space-separated names in SelectInput dropdowns. PascalCase enum values are automatically formatted with spaces, so BlueToRed displays as "Blue To Red" and LeastRecentlyUsed displays as "Least Recently Used":
// Define your enum
public enum CacheStrategy
{
LeastRecentlyUsed, // Displays as "Least Recently Used"
LeastFrequentlyUsed, // Displays as "Least Frequently Used"
[Description("FIFO")] // Custom display name
FirstInFirstOut // Without attribute: "First In First Out"
}
// Use in SelectInput - formatting happens automatically
var strategy = UseState(CacheStrategy.LeastRecentlyUsed);
return strategy.ToSelectInput()
.WithField()
.Label("Cache Strategy");
RichText.Muted() Convenience Method
Added a new .Muted() method to RichTextBuilder (RichTextBlock / Text.Rich()) for easily creating secondary/muted colored text.
Text.Rich()
.Text("Status: ")
.Success("Active")
.Text(" • ")
.Muted("Last updated 5 minutes ago")
Callout Density Control
Callout widgets now support three density levels.
Layout.Vertical()
| Callout.Info("Compact callout for tight layouts").Small()
| Callout.Warning("Standard callout with default spacing").Medium()
| Callout.Error("High-impact callout for critical messages").Large()
Ghost Variant for Expandable Widget
The Expandable widget now supports a Ghost variant.
var notes = new Expandable("Additional Notes", notesContent)
.Ghost();
// Works great with icons too
var advancedOptions = new Expandable("Advanced Settings", settingsContent)
.Ghost()
.Icon(Icons.Settings);Concise Chart Creation with Array-Based API
For quick, pre-styled chart creation, ToLineChart() and ToBarChart() support a concise array-based syntax where you pass parameters directly instead of using the fluent API—see the Line chart and Bar chart docs:
var salesData = new[]
{
new { Month = "Jan", Sales = 186 },
new { Month = "Feb", Sales = 305 },
new { Month = "Mar", Sales = 237 }
};
// ToLineChart(dimension, measures[], style?)
return salesData.ToLineChart(
e => e.Month,
[e => e.Sum(f => f.Sales)],
LineChartStyles.Dashboard);
Charts Support Non-String Dimension Values
Line, Area, Bar, Pie, Radar, and Funnel charts render correctly when the dimension column is numeric or dates, not only strings—see chart widgets.
var salesByYear = new[]
{
new { Year = 2022, Revenue = 100000 },
new { Year = 2023, Revenue = 150000 },
new { Year = 2024, Revenue = 180000 }
};
salesByYear.ToLineChart()
.Dimension(x => x.Year)
.Value(x => x.Revenue, "Revenue");
Case-Insensitive Series Keys in Line and Bar Charts
Line chart and Bar chart widgets use case-insensitive dataKey matching so series align when JSON camelCase keys (e.g. "count") differ from PascalCase measure names (e.g. Count).
data.ToLineChart()
.Dimension(x => x.Month)
.Value(x => x.Sales, "Sales")
.Value(x => x.Target, "Target");
Local File Images in Markdown
The Markdown widget can now display images from local file paths. This feature requires a two-layer opt-in for security:
// 1. Enable on the server in Program.cs
var server = new Server()
.DangerouslyAllowLocalFiles();
// 2. Enable on the widget
var markdown = new Markdown("""
# Documentation


""")
.DangerouslyAllowLocalFiles();Supported file formats: The local file endpoint now properly handles modern image formats (WebP, AVIF), video formats (WebM), markdown files (.md), and JSONL data files.
Supported path formats:
- Forward slash paths:
C:/Screenshots/example.png - File URL protocol:
file:///Users/me/diagrams/architecture.png
Important notes:
- Images are served through a proxy endpoint (
/ivy/local-file) rather than directly accessingfile://URLs, which browsers block from HTTP pages - Local file access is disabled by default.
Markdown OnLinkClick Intercepts All Links
When you register OnLinkClick on the Markdown widget, it runs for http/https and custom schemes so you can intercept all link navigation. Previously only non-standard URLs invoked the handler. See the Markdown widget documentation for details.
var markdown = new Markdown("""
[Internal Page](/docs/guide)
[External Site](https://example.com)
""")
.OnLinkClick(url => {
if (url.StartsWith("http"))
UseToast().Show($"Opening: {url}", ToastType.Info);
UseNavigation().NavigateTo(url);
});Navigation Beacons for Type-Safe App Navigation
Navigate between apps without hard-coding app IDs using the Navigation Beacon system (Navigation). Apps can advertise their ability to handle specific entity types, enabling dynamic discovery and type-safe contextual navigation.
Register a beacon using the [NavigationBeacon] attribute and a static factory method:
public class Product { public int Id { get; set; } }
[App(icon: Icons.Package)]
[NavigationBeacon(typeof(Product), nameof(GetProductBeacon))]
public class ProductDetailsApp : ViewBase
{
public static NavigationBeacon<Product> GetProductBeacon() => new(
AppId: "product-details",
ArgsBuilder: product => new { ProductId = product.Id }
);
public override object? Build()
{
var args = UseArgs<dynamic>();
return Text.Heading($"Product #{args?.ProductId}");
}
}URL Query Parameters Now Work with UseArgs
You can now pass arguments to your Ivy app directly through URL query parameters.
// Define your args model
public class MyArgs
{
public string? NoteId { get; set; }
public int? PageNumber { get; set; }
}
// In your app
var args = UseArgs<MyArgs>();
// Accessing: /myapp?noteId=abc123&pageNumber=5
// args.NoteId will be "abc123"
// args.PageNumber will be 5More examples are in the UseArgs documentation.
BaseUrl Now Includes Base Path
AppContext.BaseUrl from your server configuration now automatically includes the base path when your app is deployed behind a reverse proxy with a path prefix.
// When deployed at https://example.com/myapp/
var context = GetAppContext();
var url = context.BaseUrl;
// Returns: "https://example.com/myapp/" (includes trailing slash)Contextual Hints in Error Dialogs
Error dialogs now include helpful troubleshooting hints for common .NET exceptions.
// When a NullReferenceException occurs in your app, users will see:
// Error message + helpful hint like:
// "An object reference was not set. In Ivy apps, common causes:
// - Accessing UseState<T>().Value before it has been initialized
// - Calling UseArgs<T>() when no args were passed to the view
// - A UseQuery result accessed before loading completes (check .Loading first)
// - A service not registered in Program.cs"Breaking Changes
AppContext.PathBase Renamed to AppContext.BasePath
The AppContext.PathBase property has been renamed to AppContext.BasePath to align with .NET naming conventions. If you're accessing this property in your code, update your references:
// Before
var context = GetAppContext();
var path = context.PathBase;
// After
var context = GetAppContext();
var path = context.BasePath;The CLI flag --path-base remains unchanged.
Bug Fixes
- Vite / reverse proxy: Ivy apps load correctly behind a path-prefixed reverse proxy (e.g.
/test/studio/). Vite now emits relative asset paths (./) instead of absolute/assets/..., matchingBasePathFilterand fixing 404s. - SelectInput:
OnFocus/OnBlurfire only when focus enters or leaves the control (not when moving between internal parts); disabled selects no longer fire these events. ?shell=false: With no default app configured, Ivy auto-selects the first visible app (order and title) instead of loading nothing.- Sheet / a11y: Every Sheet includes
SheetDescriptionor a screen-reader-only fallback ("Sheet content") so Radix Dialog satisfiesaria-describedbyand console warnings are gone. - UseDownload: CS0121 ambiguity when passing synchronous factories is resolved; the synchronous overloads are chosen correctly.
- SignalR:
BaseUrlandProjectDirectorystay set; SignalR's internalidquery parameter no longer clears app argument handling. - DataTable / decimals: Decimal and currency columns render correctly; Arrow Decimal128 maps to JavaScript numbers with correct scaling (no long zero strings).
- .NET 10: Explicit reference to
Microsoft.Extensions.Configuration.AbstractionsfixesFileNotFoundExceptionwhen the transitive assembly was not copied into consuming apps on .NET 10. - Callout: Icon aligns to the top for multi-line content; titleless callouts get aligned icon and text at all densities.
- SpacerWidget: Grows by default in flex layouts to fill remaining space (horizontal and vertical).
- NumberInput: Display stays in sync when the bound value changes programmatically while unfocused (fixes malformed grouped-digit strings).
- BarChart gaps:
barGap/barCategoryGapappend%only for numeric values; string values like"10%"are no longer doubled to"10%%". - CodeBlock: Empty blocks get a minimum height from font size, line height, and padding so they stay visible and usable.
- Badge: Uses
flexwithoutw-minso badges lay out reliably inside flex parents while staying compact.
What's Changed
- Fix textarea resize marker size and value/placeholder font consistency by @dcrjodle in #2769
- Widget enhancements and hooks by @nielsbosma in #2771
- EventDispatchQueue race condition fix by @nielsbosma in #2772
- fix(callout): ensure Callout.Error renders visibly in UI by @nielsbosma in #2773
- refactor: clean up CodeBlockWidget formatting by @nielsbosma in #2774
- RichTextBlock and CodeBlock rendering improvements by @nielsbosma in #2770
- feat: init patchnotes by @rorychatt in #2766
- fix(tiptap): remove default export to eliminate MIXED_EXPORTS warning by @zachwolfe in #2776
- Add Compact, Scientific, Engineering, Accounting, and Bytes format styles to NumberInput by @nielsbosma in #2660
- Revert "Add Compact, Scientific, Engineering, Accounting, and Bytes format styles to NumberInput" by @rorychatt in #2777
- Pr/9a419fd4 numberinput format styles by @rorychatt in #2778
- Add Tooltip Property to Option by @nielsbosma in #2671
- tooltips take too long to appear should be 1 second by @dcrjodle in #2782
- Add Navigation Beacon system for type-safe app navigation by @nielsbosma in #2664
- (Tree): Align icons by @dcrjodle in #2781
- docs: add UseRefreshToken FAQ entry to DataTable.md by @rorychatt in #2783
- docs: add supported languages list to CodeInput and CodeBlock by @rorychatt in #2784
- dropdown menus need small gap between content items by @dcrjodle in #2780
- Rename query parameter from ?appshell to ?shell by @nielsbosma in #2789
- Add HTML5 details/summary tag support in Markdown by @nielsbosma in #2790
- Rerank Hallucinations.md sections by frequency by @nielsbosma in #2791
- Fix empty sequences in PivotTable aggregations by @nielsbosma in #2792
- Fix MultipleSelector dropdown scroll position on open by @nielsbosma in #2793
- Fix UseArgs URL query parameter parsing by @nielsbosma in #2796
- Fix shell=false query parameter app selection by @nielsbosma in #2797
- Add SheetDescription for accessibility by @nielsbosma in #2798
- Fix UseDownload overload ambiguity by @nielsbosma in #2799
- Add Skeleton loading variants and FAQ by @nielsbosma in #2800
- fix: let AppShell handle unknown nav app IDs instead of setting 404 … by @dcrjodle in #2788
- Add DataTable Footer Aggregates and Number Formatting by @nielsbosma in #2669
- (inputs): add OnFocus by @ArtemKhvorostianyi in #2693
- fix: add SignalR id to reserved query params by @dcrjodle in #2801
- UI widget fixes by @nielsbosma in #2805
- fix(fe): resolve frontend package vulnerabilities by @rorychatt in #2809
- fix(fe): resolve lodash-es Prototype Pollution (alert 62) by @rorychatt in #2810
- (Button): Adjust button height by @dcrjodle in #2785
- MenuItems should have semantic variants like Destructive by @dcrjodle in #2787
- feat: add hidden ReproUiBugApp for UI bug reproduction by @rorychatt in #2812
- [datatables]: vertical alignment and positioning for row action buttons by @ArtemLazarchuk in #2802
- chore: remove husky and lint-staged by @rorychatt in #2813
- fix: add MSBuild targets to build frontend during dotnet build by @rorychatt in #2811
- feat: setup Vite+ commit hooks for frontend and back-end by @rorychatt in #2814
- docs: add vp installation instructions to developer READMEs by @rorychatt in #2816
- fix(fe): ensure SpacerWidget grows by default by @rorychatt in #2817
- (SelectInput): implement ShowActions Prop, select all and clear all functionality by @ArtemKhvorostianyi in #2806
- Add Ghost variant to Expandable widget by @nielsbosma in #2818
- Add local file support for Markdown images by @nielsbosma in #2819
- Fix chart rendering issues by @nielsbosma in #2820
- Improve DataTable column width handling by @nielsbosma in #2821
- Documentation updates for charts and hallucinations by @nielsbosma in #2822
- Improve error display and accessibility by @nielsbosma in #2823
- Improve enum display names in ToOptions() by @nielsbosma in #2824
- Add Toolbar widget by @nielsbosma in #2825
- Add close callback to WithSheet by @nielsbosma in #2826
- Fix NumberInput displayValue sync by @nielsbosma in #2827
- fix(callout): align icon to top of content by @rorychatt in #2815
- feat: adding a base-path support to ivy-framework by @swaner in #2767
- fix: install node and vite-plus in docker builds by @rorychatt in #2832
New Contributors
Full Changelog: v1.2.25...v1.2.27





