Rask
Rask
v0.23.1-alpha.0.494

Layouts

Header

A full-width top navigation layout for your application.

Drawn and behaving as Flux UI's header does, measured against its live docs.

Header

The brand, the navbar and the account across the top. On a phone the links move into a sidebar that the header's toggle slides over the page.

HeaderLayoutExample.cs
namespace Rask.Site.Features.UiDocs.Examples;

public sealed partial class HeaderLayoutExample : Component
{
    // The page's body: whatever holds Ui.Main is the layout grid, and the three are siblings in it.
    protected override Component? Render() =>
        Div.Class("min-h-screen bg-white antialiased dark:bg-zinc-800")[
            Bar(),
            Navigation(),
            Ui.Main.Container(true)[
                Ui.Heading.Level(1).Xl["Good afternoon, Mara"],
                Ui.Text.Class("mt-2 mb-6 text-base")["Here's what's new today"],
                Ui.Separator.Subtle
            ]
        ];

    private static Component Bar() =>
        Ui.Header.Container(true).Class("border-b border-zinc-200 bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-900")[
            Ui.SidebarToggle.Icon(Ui.IconName.Bars2).Inset(Ui.Position.Left),
            Ui.Brand.Href("#").Logo("/img/rask-mark.svg").Name("Acme Inc.").Class("max-lg:hidden"),
            Ui.Navbar.Class("-mb-px max-lg:hidden")[
                Ui.NavbarItem.Icon(Ui.IconName.Home).Href("#").Current(true)["Home"],
                Ui.NavbarItem.Icon(Ui.IconName.Inbox).Badge("12").Href("#")["Inbox"],
                Ui.NavbarItem.Icon(Ui.IconName.DocumentText).Href("#")["Documents"],
                Ui.NavbarItem.Icon(Ui.IconName.Calendar).Href("#")["Calendar"],
                Ui.Separator.Vertical().Subtle.Class("my-2"),
                Ui.Dropdown.Class("max-lg:hidden")[
                    Ui.NavbarItem.IconTrailing(Ui.IconName.ChevronDown)["Favorites"],
                    Ui.Navmenu[
                        Ui.NavmenuItem.Href("#")["Marketing site"],
                        Ui.NavmenuItem.Href("#")["Android app"],
                        Ui.NavmenuItem.Href("#")["Brand guidelines"]
                    ]
                ]
            ],
            Ui.Spacer,
            Ui.Navbar.Class("me-4")[
                Ui.NavbarItem.Icon(Ui.IconName.MagnifyingGlass).Href("#").Label("Search"),
                Ui.NavbarItem.Icon(Ui.IconName.Cog6Tooth).Href("#").Class("max-lg:hidden").Label("Settings"),
                Ui.NavbarItem.Icon(Ui.IconName.InformationCircle).Href("#").Class("max-lg:hidden").Label("Help")
            ],
            Ui.Dropdown.Position(Ui.DropdownPosition.Top).Align(Ui.DropdownAlign.Start)[
                Ui.Profile.Avatar("/img/avatars/amber.svg"),
                Account()
            ]
        ];

    // The header's links again, for a phone: lg:hidden keeps it off a desktop.
    private static Component Navigation() =>
        Ui.Sidebar.Sticky(true).Collapsible(Ui.SidebarCollapsible.Mobile)
            .Class("border-r border-zinc-200 bg-zinc-50 lg:hidden dark:border-zinc-700 dark:bg-zinc-900")[
            Ui.SidebarHeader[
                Ui.SidebarBrand.Href("#").Logo("/img/rask-mark.svg").Name("Acme Inc."),
                Ui.SidebarCollapse.Class("sidebar-desktop:-mr-2 sidebar-rail:mr-0")
            ],
            Ui.SidebarNav[
                Ui.SidebarItem.Icon(Ui.IconName.Home).Href("#").Current(true)["Home"],
                Ui.SidebarItem.Icon(Ui.IconName.Inbox).Badge("12").Href("#")["Inbox"],
                Ui.SidebarItem.Icon(Ui.IconName.DocumentText).Href("#")["Documents"],
                Ui.SidebarItem.Icon(Ui.IconName.Calendar).Href("#")["Calendar"],
                Ui.SidebarGroup.Expandable(true).Heading("Favorites")[
                    Ui.SidebarItem.Href("#")["Marketing site"],
                    Ui.SidebarItem.Href("#")["Android app"],
                    Ui.SidebarItem.Href("#")["Brand guidelines"]
                ]
            ],
            Ui.SidebarSpacer,
            Ui.SidebarNav[
                Ui.SidebarItem.Icon(Ui.IconName.Cog6Tooth).Href("#")["Settings"],
                Ui.SidebarItem.Icon(Ui.IconName.InformationCircle).Href("#")["Help"]
            ]
        ];

    private static Component Account() =>
        Ui.Menu[
            Ui.MenuRadioGroup.Value("Mara Lind")[
                Ui.MenuRadio.Value("Mara Lind")["Mara Lind"],
                Ui.MenuRadio.Value("Theo Brandt")["Theo Brandt"]
            ],
            Ui.MenuSeparator,
            Ui.MenuItem.Icon(Ui.IconName.ArrowRightStartOnRectangle)["Logout"]
        ];
}
Live result

Inset main

Add Inset(true) to Ui.Main to place your content in a bordered panel with rounded corners. On large screens, content scrolls inside the panel, keeping its border and the space around it visible. On smaller screens, the panel becomes full width and the page scrolls normally.

HeaderInsetExample.cs
namespace Rask.Site.Features.UiDocs.Examples;

public sealed partial class HeaderInsetExample : Component
{
    // The page's body: whatever holds Ui.Main is the layout grid, and the three are siblings in it.
    protected override Component? Render() =>
        Div.Class("min-h-dvh bg-zinc-50 dark:bg-zinc-900")[
            Bar(),
            Navigation(),
            Ui.Main.Inset(true).Container(true).Class("lg:mt-0")[
                Ui.Heading.Level(1).Xl["Good afternoon, Mara"],
                Ui.Text.Class("mt-2 mb-6 text-base")["Here's what's new today"],
                Ui.Separator.Subtle
            ]
        ];

    private static Component Bar() =>
        Ui.Header.Class("bg-zinc-50 dark:bg-zinc-900")[
            Ui.SidebarToggle.Icon(Ui.IconName.Bars2).Inset(Ui.Position.Left),
            Ui.Brand.Href("#").Logo("/img/rask-mark.svg").Name("Acme Inc.").Class("max-lg:hidden"),
            Ui.Navbar.Class("-mb-px max-lg:hidden")[
                Ui.NavbarItem.Icon(Ui.IconName.Home).Href("#").Current(true)["Home"],
                Ui.NavbarItem.Icon(Ui.IconName.Inbox).Badge("12").Href("#")["Inbox"],
                Ui.NavbarItem.Icon(Ui.IconName.DocumentText).Href("#")["Documents"],
                Ui.NavbarItem.Icon(Ui.IconName.Calendar).Href("#")["Calendar"],
                Ui.Separator.Vertical().Subtle.Class("my-2"),
                Ui.Dropdown.Class("max-lg:hidden")[
                    Ui.NavbarItem.IconTrailing(Ui.IconName.ChevronDown)["Favorites"],
                    Ui.Navmenu[
                        Ui.NavmenuItem.Href("#")["Marketing site"],
                        Ui.NavmenuItem.Href("#")["Android app"],
                        Ui.NavmenuItem.Href("#")["Brand guidelines"]
                    ]
                ]
            ],
            Ui.Spacer,
            Ui.Navbar.Class("mr-4")[
                Ui.NavbarItem.Icon(Ui.IconName.MagnifyingGlass).Href("#").Label("Search"),
                Ui.NavbarItem.Icon(Ui.IconName.Cog6Tooth).Href("#").Class("max-lg:hidden").Label("Settings"),
                Ui.NavbarItem.Icon(Ui.IconName.InformationCircle).Href("#").Class("max-lg:hidden").Label("Help")
            ],
            Ui.Dropdown.Position(Ui.DropdownPosition.Top).Align(Ui.DropdownAlign.Start)[
                Ui.Profile.Avatar("/img/avatars/amber.svg"),
                Account()
            ]
        ];

    // The header's links again, for a phone: lg:hidden keeps it off a desktop.
    private static Component Navigation() =>
        Ui.Sidebar.Sticky(true).Collapsible(Ui.SidebarCollapsible.Mobile)
            .Class("bg-zinc-50 lg:hidden dark:bg-zinc-900")[
            Ui.SidebarHeader[
                Ui.SidebarBrand.Href("#").Logo("/img/rask-mark.svg").Name("Acme Inc."),
                Ui.SidebarCollapse.Class("sidebar-desktop:-mr-2 sidebar-rail:mr-0")
            ],
            Ui.SidebarNav[
                Ui.SidebarItem.Icon(Ui.IconName.Home).Href("#").Current(true)["Home"],
                Ui.SidebarItem.Icon(Ui.IconName.Inbox).Badge("12").Href("#")["Inbox"],
                Ui.SidebarItem.Icon(Ui.IconName.DocumentText).Href("#")["Documents"],
                Ui.SidebarItem.Icon(Ui.IconName.Calendar).Href("#")["Calendar"],
                Ui.SidebarGroup.Expandable(true).Heading("Favorites")[
                    Ui.SidebarItem.Href("#")["Marketing site"],
                    Ui.SidebarItem.Href("#")["Android app"],
                    Ui.SidebarItem.Href("#")["Brand guidelines"]
                ]
            ],
            Ui.SidebarSpacer,
            Ui.SidebarNav[
                Ui.SidebarItem.Icon(Ui.IconName.Cog6Tooth).Href("#")["Settings"],
                Ui.SidebarItem.Icon(Ui.IconName.InformationCircle).Href("#")["Help"]
            ]
        ];

    private static Component Account() =>
        Ui.Menu[
            Ui.MenuRadioGroup.Value("Mara Lind")[
                Ui.MenuRadio.Value("Mara Lind")["Mara Lind"],
                Ui.MenuRadio.Value("Theo Brandt")["Theo Brandt"]
            ],
            Ui.MenuSeparator,
            Ui.MenuItem.Icon(Ui.IconName.ArrowRightStartOnRectangle)["Logout"]
        ];
}
Live result

Set the background around the panel on your layout and header. Add lg:mt-0 to the main to remove the margin below the header.

Add Container(true) to center the content inside the full-width panel. Keep the header and main as direct siblings so the panel can adjust to the header's height.

Secondary sidebar

Use a sidebar for secondary navigation.

HeaderSecondarySidebarExample.cs
namespace Rask.Site.Features.UiDocs.Examples;

public sealed partial class HeaderSecondarySidebarExample : Component
{
    // The page's body: whatever holds Ui.Main is the layout grid, and the three are siblings in it.
    protected override Component? Render() =>
        Div.Class("min-h-screen bg-white antialiased dark:bg-zinc-800")[
            Bar(),
            Navigation(),
            Ui.Main.Container(true)[
                Div.Class("flex items-start max-md:flex-col")[
                    Div.Class("me-10 w-full pb-4 md:w-[220px]")[
                        Ui.Navlist[
                            Ui.NavlistItem.Href("#").Current(true)["Dashboard"],
                            Ui.NavlistItem.Href("#").Badge("32")["Orders"],
                            Ui.NavlistItem.Href("#")["Catalog"],
                            Ui.NavlistItem.Href("#")["Payments"],
                            Ui.NavlistItem.Href("#")["Customers"],
                            Ui.NavlistItem.Href("#")["Billing"],
                            Ui.NavlistItem.Href("#")["Quotes"],
                            Ui.NavlistItem.Href("#")["Configuration"]
                        ]
                    ],
                    Ui.Separator.Class("md:hidden"),
                    Div.Class("flex-1 self-stretch max-md:pt-6")[
                        Ui.Heading.Level(1).Xl["Good afternoon, Mara"],
                        Ui.Text.Class("mt-2 mb-6 text-base")["Here's what's new today"],
                        Ui.Separator.Subtle
                    ]
                ]
            ]
        ];

    private static Component Bar() =>
        Ui.Header.Container(true).Class("border-b border-zinc-200 bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-900")[
            Ui.SidebarToggle.Icon(Ui.IconName.Bars2),
            Ui.Brand.Href("#").Logo("/img/rask-mark.svg").Name("Acme Inc.").Class("max-lg:hidden"),
            Ui.Navbar.Class("-mb-px max-lg:hidden")[
                Ui.NavbarItem.Icon(Ui.IconName.Home).Href("#").Current(true)["Home"],
                Ui.NavbarItem.Icon(Ui.IconName.Inbox).Badge("12").Href("#")["Inbox"],
                Ui.NavbarItem.Icon(Ui.IconName.DocumentText).Href("#")["Documents"],
                Ui.NavbarItem.Icon(Ui.IconName.Calendar).Href("#")["Calendar"],
                Ui.Separator.Vertical().Subtle.Class("my-2"),
                Ui.Dropdown.Class("max-lg:hidden")[
                    Ui.NavbarItem.IconTrailing(Ui.IconName.ChevronDown)["Favorites"],
                    Ui.Navmenu[
                        Ui.NavmenuItem.Href("#")["Marketing site"],
                        Ui.NavmenuItem.Href("#")["Android app"],
                        Ui.NavmenuItem.Href("#")["Brand guidelines"]
                    ]
                ]
            ],
            Ui.Spacer,
            Ui.Navbar.Class("me-4")[
                Ui.NavbarItem.Icon(Ui.IconName.MagnifyingGlass).Href("#").Label("Search"),
                Ui.NavbarItem.Icon(Ui.IconName.Cog6Tooth).Href("#").Class("max-lg:hidden").Label("Settings"),
                Ui.NavbarItem.Icon(Ui.IconName.InformationCircle).Href("#").Class("max-lg:hidden").Label("Help")
            ],
            Ui.Dropdown.Position(Ui.DropdownPosition.Top).Align(Ui.DropdownAlign.Start)[
                Ui.Profile.Avatar("/img/avatars/amber.svg"),
                Account()
            ]
        ];

    // The header's links again, for a phone: lg:hidden keeps it off a desktop.
    private static Component Navigation() =>
        Ui.Sidebar.Sticky(true).Collapsible(Ui.SidebarCollapsible.Mobile)
            .Class("border-r border-zinc-200 bg-zinc-50 lg:hidden dark:border-zinc-700 dark:bg-zinc-900")[
            Ui.SidebarHeader[
                Ui.SidebarBrand.Href("#").Logo("/img/rask-mark.svg").Name("Acme Inc."),
                Ui.SidebarCollapse.Class("sidebar-desktop:-mr-2 sidebar-rail:mr-0")
            ],
            Ui.SidebarNav[
                Ui.SidebarItem.Icon(Ui.IconName.Home).Href("#").Current(true)["Home"],
                Ui.SidebarItem.Icon(Ui.IconName.Inbox).Badge("12").Href("#")["Inbox"],
                Ui.SidebarItem.Icon(Ui.IconName.DocumentText).Href("#")["Documents"],
                Ui.SidebarItem.Icon(Ui.IconName.Calendar).Href("#")["Calendar"],
                Ui.SidebarGroup.Expandable(true).Heading("Favorites")[
                    Ui.SidebarItem.Href("#")["Marketing site"],
                    Ui.SidebarItem.Href("#")["Android app"],
                    Ui.SidebarItem.Href("#")["Brand guidelines"]
                ]
            ],
            Ui.SidebarSpacer,
            Ui.SidebarNav[
                Ui.SidebarItem.Icon(Ui.IconName.Cog6Tooth).Href("#")["Settings"],
                Ui.SidebarItem.Icon(Ui.IconName.InformationCircle).Href("#")["Help"]
            ]
        ];

    private static Component Account() =>
        Ui.Menu[
            Ui.MenuRadioGroup.Value("Mara Lind")[
                Ui.MenuRadio.Value("Mara Lind")["Mara Lind"],
                Ui.MenuRadio.Value("Theo Brandt")["Theo Brandt"]
            ],
            Ui.MenuSeparator,
            Ui.MenuItem.Icon(Ui.IconName.ArrowRightStartOnRectangle)["Logout"]
        ];
}
Live result

Behaviour

The layout grid

Ui.Header, Ui.Sidebar and Ui.Main are siblings, and whatever holds the Ui.Main (the body, or a wrapper as in these examples) becomes the layout grid: the header across the top, the sidebar down the side, the main in what is left. A header written before the sidebar runs the full width above it; written after, it sits beside it, which is the sidebar layout. Keep the three direct siblings.

Nothing is painted that Flux does not paint: the header's ground and border are your classes, exactly as in Flux's examples. Sticky(true) keeps the header at the top while the page scrolls under it, and Container(true) holds what is in it to the container width, centred, while its ground runs edge to edge.

The inset main

Ui.Main.Inset(true) draws the content on a bordered, rounded panel with a gutter round it. From lg up the grid fills the viewport (100dvh) and the panel scrolls inside itself; below it the panel is the full width and the page scrolls. Ui.Main is a <div>, as Flux writes it: put a <main> inside it (Ui.Main[Main[Outlet]]) for the landmark Rask focuses after a navigation.

On a phone

Below lg the header's navbar is hidden (max-lg:hidden, your class) and its links are a Ui.Sidebar.Collapsible(Ui.SidebarCollapsible.Mobile) that lg:hidden keeps off a desktop. Ui.SidebarToggle slides it over the page: a <label> for the sidebar's checkbox, with a button's role and a tab stop, which the runtime presses on Enter and Space. From the sidebar's breakpoint up there is nothing to slide and the toggle is not shown, so Flux's lg:hidden on it is not needed.

The icon-only items on the right of the header take Flux's label: .Label("Search") writes it on the link exactly as Flux does, a label attribute and nothing else (no aria-label, no tooltip). Flux writes logo:dark on the sidebar's brand only (Ui.SidebarBrand.LogoDark); in the header it writes a second flux:brand for dark mode, and these examples write one, with a mark that reads on both grounds.

The demos

A layout is a whole document, so each result above is its demo page in a frame: a desktop scaled to the column, or the column's own width on a phone. Fullscreen opens the page itself, as Flux's Fullscreen opens its demo.

Reference

Ui.Header

flux:header

The bar across the top: a brand, the navigation, the account. It paints no ground of its own.

Step
Takes
What it does
.Sticky()boolKeeps the header at the top of the viewport while the page scrolls under it.
.Container()boolHolds what is in it to the container width, centred, while its ground runs edge to edge.

Ui.SidebarToggle

flux:sidebar.toggle

The button that slides the sidebar over the page: the hamburger in a phone's header. Not shown from the sidebar's breakpoint up.

Step
Takes
What it does
.Icon(…)Ui.IconName — one of 317 namesThe icon drawn. Bars2 when unset.
.Inset(…)
Ui.Position
Default
Top
Right
Bottom
Left

Each is a step of its own too, unless an HTML tag has its name.

Pulls the button into its container's padding on that side: Flux's inset="left".

Ui.Main

flux:main

The page's content, beside the sidebar and under the header. A <div>: put a <main> inside it.

Step
Takes
What it does
.Inset()boolA bordered, rounded panel with a gutter round it; from lg up the layout fills the viewport and the panel scrolls.
.Container()boolHolds the content to the container width, centred: inside the panel, when inset.
Confirm