Imported from rorychatt/AIOS (
AGENTS.md). Install upstream withnpx skills add rorychatt/AIOS. Copyright stays with the author.
Below are notes about Ivy Framework.
Introduction to the Ivy Framework for LLMs
- Ivy is a declarative full-stack UI framework that allows developers to build user interfaces using a component-based approach very similar to React.
- In Ivy, you only write one application in pure C# and we don't have a BE and FE distinction.
- UI rendering is handled by Ivy.
- When programming in Ivy you focus on building the logical structure of your application using a large set of pre-built widgets and views - you rarely need to specify any styling - Ivy just makes it look good by default.
Terminology:
- "Views" are the main building blocks of the UI, similar to React components.
- "Hooks" are functions that allow views to manage state and side effects.
- "Widgets" are the UI elements that make up the views. e.g., Button, TextBlock, StackPanel...
- "Apps" are the top-level views that represent entire applications.
A view is defined as a class that inherits from ViewBase and implements a Build method. The Build method returns either another view or a widget.
WARNING: There is NO
AppBaseclass. ALL views and apps inherit fromViewBase.
Widgets can have multiple children, but views can only return a single object (widget or view). To return multiple widgets from a view, you can use a Fragment use the Layout helpers. See below.
public class MyView : ViewBase { public override object? Build() { var count = UseState(0); return Layout.Vertical() | new Text($"Count: {count.Value}") | new Button("Increment", () => count.Set(count.Value + 1)); } }
The topmost view in an Ivy application is called an App and is decorated with the [App] attribute. The attribute uses named parameters:
[App(title: "Customers", icon: Icons.Rocket)] public class CustomersApp : ViewBase
titleis optional — if omitted, it is derived from the class name (e.g.CustomersApp→ "Customers").iconuses theIconsenum — these are Lucide icons in PascalCase (e.g.Icons.Link,Icons.Settings,Icons.Rocket).
An app is built into a tree of widgets. This is what's rendered to the screen.
Application Structure
A typical Ivy project has this folder structure:
MyProject/ ├── Program.cs # Entry point — configures and starts the Ivy server ├── MyProject.csproj # Project file ├── Apps/ # All app classes go here (convention) │ ├── DashboardApp.cs │ └── Settings/ # Subfolder namespaces become URL path segments │ └── UserProfileApp.cs # → /settings/user-profile └── Connections/ └── MyDb/ ├── MyDbContext.cs ├── MyDbContextFactory.cs ├── MyDbConnection.cs └── Product.cs # Entity classes
Dependency Injection
Register services in Program.cs, consume them in views with UseService<T>():
// Program.cs server.Services.AddSingleton<IMyService, MyService>();
// In a view var myService = UseService();
Common Widgets
Button Card Badge Sheet Progress Expandable Tooltip DropDownMenu Table List Details Image Avatar Spacer Callout
Layouts
- Use Layout.Vertical() or Layout.Horizontal() to create stack layouts.
- Layout.Grid() — Layout.Wrap()
- Add Children: Pipe child elements using the | operator to arrange them top-to-bottom (vertical) or left-to-right (horizontal).
- Layouts can be customized with methods like .Gap(int number) to set spacing between children. Use .Left(), .Center(), or .Right() methods to control alignment.
- The number in Gap(int number) works the same as in Tailwind CSS spacing scale (e.g., 1 = 0.25rem, 2 = 0.5rem, etc.).
- Layouts have a default gap of 4 (1rem). Do NOT add
.Gap(4)— it is the default and adds unnecessary noise. Only use.Gap()when you need a value other than 4. .Padding()is rarely needed. Layouts and pages already have appropriate padding by default. Only add.Padding()when you need extra inner spacing for a specific design reason.
// Basic Vertical Layout Layout.Vertical() | new Badge("Top") | new Badge("Middle") | new Badge("Bottom");
// Nested layouts with alignment Layout.Vertical().Align(Align.Center) | Text.Label("Header") | (Layout.Horizontal() | new Button("Previous") | new Button("Next")); //NOTE: Parentheses are used to group the horizontal layout - THIS IS REQUIRED
Grids:
Layout.Grid() .Columns(2) .Rows(2) | Text.Block("Cell 1") | Text.Block("Cell 2") ...
Align values: TopLeft, TopCenter, TopRight, Left, Center, Right, BottomLeft, BottomCenter, BottomRight, Stretch
Text
The Text helper utility is used to create various semantic text elements.
- Text.H1, Text.H2, : For headings.
- Text.Lead: For prominent introductory text.
- Text.P: For standard paragraphs.
- Text.Block: For block-level content (e.g., list items).
- Text.InlineCode: For displaying inline code snippets.
- Test.Muted
Styling Modifiers: .NoWrap(): .Bold() .Italic() .StrikeThrough() .Color(Colors)
Layout.Vertical() | Text.H1("Getting Started") | Text.P("This is a paragraph of text.").NoWrap()
Colors
Ivy.Colors enum has the following values:
Black, White, Slate, Gray, Zinc, Neutral, Stone, Red, Orange, Amber, Yellow, Lime, Green, Emerald, Teal, Cyan, Sky, Blue, Indigo, Violet, Purple, Fuchsia, Pink, Rose, Primary, Secondary, Destructive, Success, Warning, Info, Muted
Density
All widgets support .Density(Density.Small), .Density(Density.Medium), .Density(Density.Large).
Convenience methods: .Small(), .Medium(), .Large().
Density adjusts the overall visual size of a widget (text, padding, etc.).
There is no ButtonSize enum — use Density for all widgets.
Size
.Width(Size.X) and .Height(Size.X) set widget dimensions.
.Size(Size.X) sets both width and height.
Common Size values:
- Size.Units(n) — Tailwind spacing scale (n × 0.25rem)
- Size.Full() — 100%
- Size.Fit() — fit-content
- Size.Auto() — auto
- Size.Px(n) — exact pixels
- Size.Fraction(0.5f) — percentage, Size.Half(), Size.Third()
Size is NOT the same as Density. Size controls dimensions; Density controls visual density.
Event Handling
new Button("Click Me") .Primary() .OnClick(() => { count.Set(count.Value + 1); })
new TextInput().Default() .Value(text.Value) .OnChange(text.Set) .OnBlur(() => Console.WriteLine("Blurred"))
Hooks
- Most hooks that you know from React are available in Ivy. They follow the same principles as in React.
- Hooks should only be called at the top level of a Build() method, not inside loops or conditions.
UseState
var nameState = UseState("World"); var iconsState = UseState<Icons[]>();
If you don't specify a value, default(T) is used.
UseState hook returns a state object IState that provides:
WARNING: UseState returns
IState<T>, NOTState<T>. There is noState<T>type in Ivy.
- .Value property to read the current state.
- .Set(newValue) method to update the state in UseEffect or in an event handler.
Always use immutable types (e.g. records) with UseState — mutable classes that are modified in-place and passed back via .Set() will not trigger a re-render because the reference hasn't changed. Instead, create a new instance (e.g. using with expressions on records) before calling .Set().
UseEffect
void UseEffect(Action effect, params IEffectTriggers[] triggers) void UseEffect(Func asyncEffect, params IEffectTriggers[] triggers) void UseEffect(Func effectWithCleanup, params IEffectTriggers[] triggers) void UseEffect(Func<Task> asyncEffectWithCleanup, IEffectTriggers object[] triggers)
-
EffectTrigger.OnBuild() - runs after every build
-
EffectTrigger.OnMount() - runs once when the view is first mounted
-
EffectTrigger.OnStateChange(IState) - runs when the specified state changes
-
IState is automatically converted to EffectTrigger.OnStateChange
-
If no triggers are provided, the effect trigger is assumed to be OnMount.
UseQuery
UseQuery is the preferred pattern for data fetching in Ivy. It should be favored over the UseEffect + UseState fetch pattern.
var query = UseQuery( key: "my-data", fetcher: async (ct) => await LoadDataAsync(ct) );
if (query.Loading) return Skeleton.Card(); if (query.Error is { } error) return Callout.Error(error.Message);
// Use query.Value
QueryResult properties:
- .Value — the fetched data (default until loaded)
- .Loading — true during initial fetch (no value yet)
- .Validating — true during background revalidation
- .Error — exception if the fetch failed
- .Mutator — provides .Revalidate(), .Invalidate(), .Mutate(value, revalidate)
Key conventions:
- String:
"my-data" - Tuple:
(nameof(MyBlade), entityId)
Common options (QueryOptions):
- KeepPrevious: true — show stale data while revalidating with a new key
- RevalidateOnMount: false — skip initial fetch when using initialValue
- RefreshInterval: TimeSpan — poll at an interval
- Scope: QueryScope.View — isolate cache to the view instance (default is Server)
Tag-based invalidation (cross-component): var queryService = UseService(); queryService.RevalidateByTag(typeof(Product[])); // collection queryService.RevalidateByTag((typeof(Product), id)); // single entity
Static "hooks" pattern (reusable across views): public static QueryResult<T[]> UseMyRecords(IViewContext context, string filter) { return context.UseQuery( key: (nameof(UseMyRecords), filter), fetcher: async ct => { /fetch/ }, tags: [typeof(T[])], options: new QueryOptions { KeepPrevious = true } ); }
Dependent fetching (wait for another query): var user = UseQuery(key: "user", fetcher: async ct => await GetUser(ct)); var projects = UseQuery( () => user.Value?.Id, // null = idle, no fetch async (userId, ct) => await GetProjects(userId, ct));
UseRef UseContext UseQuery UseMutation UseSignal UseService UseArgs UseDownload UseRefreshToken UseTrigger UseWebhook UseAlert
Inputs
Ivy has several Input widgets for handling user input. There are rarely used directly - instead we use extension methods on IState to bind state to inputs.
var userNameState = UseState(""); var input = userNameState.ToTextInput().Placeholder("Enter your name");
Most inputs have extension methods for common configurations: userNameState.ToTextInput().Required().MaxLength(50).Placeholder("Enter your name");
TextInput NumberInput BoolInput SelectInput AsyncSelectInput DateTimeInput DateRangeInput ColorInput CodeInput FeedbackInput FileInput
Common Hallucinations
- Base class is
ViewBase(NOTAppBasethere is noAppBaseclass) Textis a static helper - useText.P(),Text.H2(), ...UseState<T>()returnsIState<T>, NOTState<T>- All types are in the
Ivynamespace Colorsis a flat enum (e.g.Colors.Red,Colors.Blue) we have no shade levelsDbContextmust never be injected directly! Always resolveIDbContextFactory<T>viaUseServiceand create scoped instances withCreateDbContextAsync()inside query/mutation lambdas
Further Reading
Forms DataTable Table Details - Display structured label-value pairs Program.cs Size Align Downloads Icons
All Ivy documentation pages are listed on: https://docs.ivy.app/sitemap.xml. Add ".md" to the end of any URL to go directly to the Markdown version of the doc.