Changelog Azalea Kit UI 101 Usage Customizing Utilities Form controls Laravel Vue FHIR Types Accessibility Icons Actions Button Button group Layout and structure Card Card section Care summary Care timeline Divider Empty state Footer Header Layout Stack Table Selection and input Calendar Checkbox Checkbox group Color picker Combobox Combobox option Input Input mask Memorable date Number input Option Phone input Radio Radio button Radio group Range Select SSN input Tag Textarea Toggle ZIP code input Images and icons Avatar Icon Icon button Logo Feedback indicators Badge Banner Progress bar Skeleton Skeleton body text Skeleton display text Skeleton thumbnail Spinner Typography Text Navigation Breadcrumb Breadcrumb item Dropdown Menu Menu item Menu label Navigation Nav group Nav item Nav toggle Pagination Tab Tab group Tab panel Top bar Tree Tree item User menu Overlays Dialog Popover Tooltip Utilities Collapsible FHIR address FHIR human name FHIR quantity FHIR questionnaire Format bytes Format date Format number Relative time Resize observer Split panel Tooltip Border Color Shadow Spacing Transition Typography Z-index Contributing Visual Tests Light Dark System

Layout

<ahi-layout> | AhiLayout

Layout is used to create the main layout of an app.

<!-- Inspect the templates/layout/index.html file to see the actual source code for this example -->
<iframe
  loading="lazy"
  style="visibility:hidden;min-height:550px;" onload="this.style.visibility='visible';"
  sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
  title="Layout template demo"
  src="../../templates/layout/">
</iframe>
Open in new window

Below 768px the navigation is a drawer over the page. Once the toggle or nav-open has opened it, a click on a nav item without a subnav closes it again, because the reader has picked a destination. It stays open when the link opens somewhere else and leaves this page where it is, which happens in two ways:

  • The nav item’s target loads the link somewhere else. _blank opens a new tab, and a name opens or reuses the window or frame of that name. _self, _parent and _top load in place of this page, so they close the drawer like a link with no target.
  • Ctrl, Cmd or Shift is held. Ctrl-click, or Cmd-click on macOS, opens a link in a new tab, and Shift-click opens one in a new window in most browsers.

A nav item with no href and no subnav renders a button, which opens nothing elsewhere, so a click on it closes the drawer whatever keys are held. A middle-click fires auxclick rather than click, so it leaves the drawer open. On a wide screen a click on a nav item never closes the navigation.

The drawer also stays open when a listener on the nav item, or on the navigation around it, prevents the click’s default action, since that listener runs before the layout sees the click. If it does so to route within the app, the reader does move to a new view, so close the drawer yourself: set the layout’s navOpen to false while its isNarrow is true. On a wide screen that would collapse the navigation instead. Closing it this way does not move focus, so also move focus to the new view in the main content. Moving focus there also hides a drawer that keyboard focus brought into view, described below. Setting navOpen does nothing for that drawer, since it is already false.

Moving keyboard focus into the closed navigation brings the drawer into view without opening it. A click on a nav item, or Enter on one, hides it by the same rules as the opened drawer: a same-tab link hides it, and a link that opens somewhere else leaves it in view. It also hides once focus or a click moves to the main content. On a narrow screen the pointer does not bring the drawer into view, so moving it out of the drawer does not hide it either.

Escape closes the opened drawer, and so does a click that starts and ends outside it. Escape does nothing to a drawer that keyboard focus brought into view, since that drawer was never opened.

When a click on a nav item hides either drawer while keyboard focus is in it, focus moves to the navigation toggle, so it is not left on an item that is now off-screen. Escape, or a click outside, does the same when it closes the opened drawer. The page does not scroll to the toggle. A click outside usually moves focus itself first, and focus then stays where the click put it. A toggle of your own in the nav-toggle slot takes focus the same way. If nothing in that slot is in the tab order, focus is dropped instead. Where the next Tab goes then depends on the browser, and in some it lands on a nav item and brings the drawer back. A fragment link, or a client-side router that moves focus to the new view, still moves it there, since it runs after the click.

On a wide screen the navigation is either opened at its full width or hidden off-screen, and the hidden state returns on hover through a brief “peek”. A touch tablet has no hover, so peek is unreachable there: the navigation is either 250px of a 834px screen or two taps away behind the toggle.

An open <ahi-dropdown> slotted into the navigation holds a peek in view: the pointer leaving does not hide it while the dropdown is open, and it hides shortly after the dropdown closes instead, unless focus has moved within the navigation since the pointer left. A dropdown inside another component’s shadow root does not hold it. A dropdown whose slot is changed so that it leaves the navigation while open gives the navigation no sign of it, so the peek then waits for focus or a click in the main content, or for the pointer to cross it again.

Add nav-rail and the collapsed state becomes a narrow icon-only rail instead of nothing. Every top-level item stays visible and one tap away, and the content column keeps most of the width it would have gained by hiding the navigation altogether.

<ahi-layout nav-rail persist-nav-state>
  <ahi-navigation slot="nav">
    <ahi-nav-item icon="home" href="/">Home</ahi-nav-item>
    <ahi-nav-item icon="calendar" href="/schedule">Schedule</ahi-nav-item>
  </ahi-navigation>
  <div>Main content</div>
</ahi-layout>

nav-rail is a preference, not a state. nav-open still says whether the navigation is expanded; nav-rail says what the collapsed form looks like. That is why toggling out of the expanded state returns to the rail rather than to nothing, and why persist-nav-state needs no extra key — the nav-open value it already stores restores the right one of the two.

Some consequences worth knowing:

  • It applies to wide screens only. Below 768px the navigation is a drawer, and there is no room for a rail beside it. nav-rail is ignored there.
  • A railed layout starts collapsed. The rail is already on screen, so starting collapsed costs the reader nothing and keeps the content column at full width, which is the point of asking for one.
  • Peek does not apply. There is nothing hidden to reveal, so hover and focus leave the rail alone.
  • The navigation is not resizable while railed. The rail is a fixed width, so the resize handle is hidden the same way it is on a narrow screen.
  • The toggle moves into the top of the rail. It is what expands the rail back to its labels, so it is placed as the rail’s first item rather than left floating over the content. That is the same position ahi-navigation renders its header slot in — a logo there and the toggle will sit on top of each other, so hide the header while railed if you use one.

Choosing when to use it

nav-rail is deliberately consumer-selected rather than a width AKU picks for you. Whether a rail beats a hidden navigation depends on how many top-level destinations an app has and how wide its content needs to be — questions the component cannot answer. Set the attribute from whatever breakpoint policy your app already has.

Sizing

The rail is one interactive target plus the padding around it:

--layout-nav-rail-width: calc(var(--ahi-target-min-size) + var(--ahi-space-4) * 2 + var(--ahi-space-2) * 2);

Because --ahi-target-min-size follows the pointer rather than the viewport, a touch device gets a rail sized for a thumb and a mouse-driven desktop gets a more compact one, without either being inferred from screen width. Override --layout-nav-rail-width if you need a different figure.

Telling slotted navigation to render icon-only

While railed, the layout sets --ahi-nav-rail: 1 on its navigation region. Nav Item reads it and drops its label, keeping the icon. The signal is a custom property rather than an attribute because it inherits through the flat tree — down into the slotted <ahi-navigation> and on into each <ahi-nav-item>’s own shadow root — which no attribute selector can do across two shadow boundaries.

Custom navigation content can read the same property:

.my-nav-label {
  display: block;
}

/* 1 while the layout is railed, otherwise unset. */
@container style(--ahi-nav-rail: 1) {
  .my-nav-label {
    display: none;
  }
}

Give any item you want to survive the rail an icon; an item with no icon has nothing left to show once its label is dropped.

Theming

Accent color

AKU provides a concept called accent color that supports both light and dark themes. To get started with using accent color, first remove all themes from the <head> section of the application and only leave the default light theme that we’ll use as a basis:

<link rel="stylesheet" href="/node_modules/@azaleahealth/azalea-kit-ui/dist/themes/light.css" />

The theme ships defaults for these — --ahi-color-blue-600 for the accent and white on top of it — so a plain install already paints correctly and you can skip this section entirely.

To use your own brand color instead, add the following after the light theme, replacing accent-color with a hex value and text-on-accent-color with one that offers the necessary 4.5:1 contrast ratio against it:

<style>
  :root {
    --ahi-color-accent: accent-color;
    --ahi-color-text-on-accent: text-on-accent-color;
  }
</style>

Top bar color

For applications that utilize the Top Bar component, AKU provides slightly more control over the visual appearance. For this purpose, we have a CSS Custom Property named var(--ahi-color-accent-secondary), which defaults to --ahi-color-blue-700 — the darker step, because this is the surface that carries body-sized white text, where it measures 7.58:1. It can be overridden to customize the background color of the top bar and navigation header:

AKU light theme AKU dark theme

To get started using this feature, add the following styles and replace color with a custom hex value that offers at least 4.5:1 contrast ratio against white text:

<style>
  :root {
    --ahi-color-accent-secondary: color;
  }
</style>

Note that the text and interface elements inside the top bar and navigation header use white text by default. Although we do not recommend changing this, you can customize this color by using the --ahi-color-text-on-accent-secondary token.

Best practices

  • Layout uses the <main> element internally which is an HTML sectioning element and defines an ARIA landmark role. For that reason, you should never put the layout component inside another HTML landmark.
  • To visually group content in a layout section, use the Card component.
  • To layout a set of smaller components, use the Stack component.

Importing

Script Bundler

To import this component using a script tag:

<script type="module" src="/path/to/azalea-kit-ui/dist/components/layout/layout.js"></script>

To import this component using a bundler:

import '@azaleahealth/azalea-kit-ui/dist/components/layout/layout.js';

Slots

Name Description
(default) The default main section content.
nav Used to place content inside the navigation sidebar.
top-bar Used to place the Top Bar component.
header Used to place content inside the header section. This slot can be made sticky by utilizing the sticky property.
footer Used to place content inside the footer section. This slot can be made sticky by utilizing the stickyFooter property.
nav-toggle Used to place a custom nav-toggle component, for cases where you might need to add a tooltip.

Learn more about using slots.

Properties

Name Description Type Default
navOpen
nav-open
Controls whether the navigation is hidden off-screen or not. Defaults to true for wide viewports, and false otherwise. boolean -
persistNavState
persist-nav-state
Controls whether the navigation’s open/closed state is persisted across page loads. This is useful for multi-page apps, where clicks on links trigger a full page load. boolean false
syncNavState
sync-nav-state
Controls whether the navigation’s open/closed state and width is synced across tabs/windows on the same origin. This is useful for long-lived app sessions where you may have multiple tabs/windows open at once. boolean false
navRail
nav-rail
Controls whether a wide-screen navigation that is not opened stays on screen as a narrow icon-only rail, rather than hiding off-screen behind the toggle. This is a preference, not a state: nav-open still says whether the nav is expanded, and this says what the collapsed form looks like. That is what makes toggling out of opened return to the rail rather than to nothing, and it is why persist-nav-state needs no new key — the persisted nav-open already restores the right one of the two. Off by default, so a consumer that does not opt in renders exactly as before. Only wide screens are affected; below 768px the nav is a drawer and there is no room for a rail. boolean false
padding Controls the padding of the default main section slot. When set to “none”, the nav and header slots will still have padding. 'm' | 'none' 'm'
sticky Controls whether the layout’s header has sticky positioning. boolean false
stickyFooter
sticky-footer
Controls whether the layout’s footer has sticky positioning. boolean false
isNarrow A getter whose values reflects whether the layout component considers the viewport to be narrow or not. A narrow viewport is considered to be less than 768px wide. boolean -
updateComplete A read-only promise that resolves when the component has finished updating.

Learn more about properties and attributes.

Dependencies

This component automatically imports the following dependencies.

  • <ahi-button>
  • <ahi-icon>
  • <ahi-nav-toggle>
  • <ahi-spinner>