Fusion UI LogoFusion UI Logo
Fusion UI LogoFusion UI Logo
/
Getting Started
Concepts
Components
Example Apps
Getting Started
Concepts
Components
Example Apps
Fusion UI LogoFusion UI Logo
Getting Started
Concepts
Components
Example Apps
Overview
Foundations
Color
Typography
Spacing & Radius
Core Concepts
Surfaces & Elevation
States
Nesting
Views & Offsets
Animations
Touch & Mobile

Views & Offsets

How fixed views stack and how content keeps clear of them.

View stacking

Views are the fixed frame of an application: BannerView, MainNavView, SideMenuView and PageHeaderView share the screen and must stack without overlapping or fighting over z-index. Instead of hard coded offsets, every view publishes its size as CSS custom properties, and a shared SCSS function adds them up, so each view and each sticky element knows exactly where to sit.

BannerViewtop: 0
MainNavViewtop: banner
SideMenuViewleft: 0
PageHeaderViewtop: banner + main nav · left: side menu
Page contenttop: banner + main nav + page header

See it in action

Scroll the page, show the banner and switch to phone width: the sticky bar always sits right below the page header.

/preview/view-stack

File: view-stack-demo.tsx

"use client";
import React from "react";
import {
Banner,
Button,
Container,
IconDashboard,
IconFileList,
MainNavView,
PageHeaderView,
SideMenuItem,
SideMenuSection,
SideMenuView,
StackLayout,
Tile,
TileContent,
TileHeader,
useBanner,
} from "@brynlabs/fusion-ui";
import styles from "./view-stack-demo.module.scss";
const BannerToggle = () => {
const {showBanner, hideBanner} = useBanner();
return (
<Button
size={"small"}
onClick={() =>
showBanner(
<Banner bannerStyle={"warn"}>
Maintenance tonight from 10 pm.{" "}
<Button size={"extraSmall"} color={"currentColor"} onClick={hideBanner}>
Hide
</Button>
</Banner>,
)
}>
Show banner
</Button>
);
};
export const ViewStackDemo = () => (
<MainNavView
logo={<span className={"heading-4-font"}>Shop</span>}
menuItems={[
{title: "Reports", href: "/preview/view-stack"},
{title: "Orders", href: "#orders"},
]}>
<SideMenuView
menuItems={
<SideMenuSection sectionTitle={"Reports"}>
<SideMenuItem
menuItem={{title: "Revenue", href: "/preview/view-stack", icon: <IconDashboard />}}
/>
<SideMenuItem
menuItem={{title: "Customers", href: "#customers", icon: <IconFileList />}}
/>
</SideMenuSection>
}>
<PageHeaderView pageHeaderTitle={"Revenue"} buttons={<BannerToggle />}>
<Container direction={"both"}>
<StackLayout>
<div className={styles.sticky}>
Sticky with get-view-offset(&quot;top&quot;): always right below the page header
</div>
{Array.from({length: 10}, (_, index) => (
<Tile key={index}>
<TileHeader>Section {index + 1}</TileHeader>
<TileContent>
Scroll, show the banner and make the window narrow: every view keeps clear of the
others.
</TileContent>
</Tile>
))}
</StackLayout>
</Container>
</PageHeaderView>
</SideMenuView>
</MainNavView>
);

How it works

get-view-offset($direction) takes "top", "bottom", "left", "right" or "z-index" and returns a calc() that adds the offsets of all registered views for that direction. The registered views, in stacking order:

ViewCustom properties
side-menu--bryn-side-menu-view-offset--top / --left / … / --z-index
menu-bar--bryn-menu-bar-view-offset--top / --left / … / --z-index
main-nav--bryn-main-nav-view-offset--top / --left / … / --z-index
loading-bar--bryn-loading-bar-view-offset--top / --left / … / --z-index
banner--bryn-banner-view-offset--top / --left / … / --z-index
page-header--bryn-page-header-view-offset--top / --left / … / --z-index
small-screen-notice--bryn-small-screen-notice-view-offset--top / --left / … / --z-index

Each view sets the offsets for the content inside it. When something calls top: get-view-offset("top"), the function produces:

calc(0px
+ var(--bryn-side-menu-view-offset--top, 0px)
+ var(--bryn-menu-bar-view-offset--top, 0px)
+ var(--bryn-main-nav-view-offset--top, 0px)
+ var(--bryn-loading-bar-view-offset--top, 0px)
+ var(--bryn-banner-view-offset--top, 0px)
+ var(--bryn-page-header-view-offset--top, 0px)
+ var(--bryn-small-screen-notice-view-offset--top, 0px))

For "z-index" the same summing runs, but it starts at 100. Each view therefore layers naturally above the previous one — no magic numbers required.

Sticky content

Use the function for your own sticky or fixed elements, so they keep clear of all views — whichever are on the page. This is how the on-page menu of this documentation sticks below the page header.

Always transition the properties that use it with var(--transition--x-fast). The offsets change while the views animate, e.g. when the main nav slides away on scroll. With the same transition the element moves along with the views, without it the element jumps.

@use "@brynlabs/fusion-ui" as fusionui; // or "pkg:@brynlabs/fusion-ui"
.sticky {
position: sticky;
top: fusionui.get-view-offset("top");
// Moves along with the views instead of jumping
transition: top var(--transition--x-fast);
}
h2 {
// Anchors are not hidden behind the views when jumped to
scroll-margin-top: fusionui.get-view-offset("top");
}

Example: a view

A view exposes its height as top offset on its content wrapper, so everything inside starts below it. The bar itself uses the function to sit below everything outside of it, such as a banner.

File: main-nav-view.module.scss

.content {
--bryn-main-nav-view-offset--top: var(--bryn-main-nav--height);
--bryn-main-nav-view-offset--z-index: -1;
}
.bar {
position: fixed;
top: get-view-offset("top");
right: get-view-offset("right");
left: get-view-offset("left");
z-index: get-view-offset("z-index");
transition:
top var(--transition--x-fast),
right var(--transition--x-fast),
left var(--transition--x-fast);
}
On this page
  • View stacking
  • See it in action
  • How it works
  • Sticky content
  • Example: a view