Base Layout (New)
The Base Layout component establishes a standardized structure at the top of the UI, aiding user navigation, providing context, and offering quick access to essential actions.
#Composition
The Base Layout is ideal for composing the following elements:
#Horizontal Navigation
Reserved for global actions and branding elements, maintaining consistent placement across the platform.
#Side navigation
The primary means for navigating the platform's core features and sections.
#Header
Contains the following elements:
- Page Title: Clearly identifies the current page or section, using the same label as the corresponding side navigation item for consistency. Keep titles concise and informative.
- Breadcrumbs (Optional): Display the user's navigation path within the platform, enhancing context and discoverability. Recommended for most pages unless the navigation is shallow.
- Utilities (Optional): Contextual actions or tools specific to the current page or section. Positioned based on their scope, with global utilities on the right and page-specific utilities on the left.
- Filters (Required): Enable users to refine and explore data on the page, improving discoverability. Prioritize the most frequently used filters and ensure they adapt to different screen sizes.
- Dashboard Picker (Optional): Allows users to personalize their view by selecting a preferred dashboard upon entering the platform.
- Alerts (Optional, Use Sparingly): Display critical system-wide messages that persist above the content area. Use sparingly to avoid overwhelming users.
#Examples
#Header base
Features a persistent page title, serving as a constant reference point for users throughout their navigation. Ensure consistency by using the same label from the side navigation as the page title.
Page title
<BaseLayoutNew id="content" pageHeader={<PageHeader title="Page title" />}>
<>Page content 🦔</>
</BaseLayoutNew>
#Header with navigation
Includes breadcrumbs to provide users with a clear understanding of their navigation path and context. This is especially useful in more complex modules or sections of the platform.
Page title
<BaseLayoutNew
id="content"
pageHeader={
<PageHeader
title="Page title"
breadcrumbs={{
"aria-label": "Breadcrumbs",
items: [
{ title: "Level 1", url: "https://fancylib.netlify.app" },
{ title: "Level 2", url: null },
],
}}
/>
}
>
<>Page content 🦔</>
</BaseLayoutNew>
#Header with utilities
Presents utilities and actions for user interaction, positioned based on their scope: product-specific utilities on the left and global utilities on the right. Prioritize the most important actions for the current context.
- Global utilities:Â Located on the far left, these are universal functions, such as export, search, settings, and similar features.
- Product-specific utilities: Located on the far right, these are functionalities relevant to the current page or product users are interacting with.
Page title
<BaseLayoutNew
id="content"
pageHeader={
<PageHeader
title="Page title"
utilities={{
global: (
<>
<Button variant="borderless" aria-label="Icon only button">
<Icon>
<IconSearch />
</Icon>
</Button>
<Button variant="borderless" aria-label="Icon only button">
<Icon>
<IconDownload />
</Icon>
</Button>
<Button variant="borderless" aria-label="Icon only button">
<Icon>
<IconSettings />
</Icon>
</Button>
<Button variant="borderless" aria-label="Icon only button">
<Icon>
<IconOptions />
</Icon>
</Button>
</>
),
productSpecific: (
<>
<Button variant="primary">Primary action</Button>
<Button variant="secondary">Secondary action</Button>
</>
),
}}
/>
}
>
<>Page content 🦔</>
</BaseLayoutNew>
#Header with filters
Features a filter bar to enable users to refine and explore data displayed on the page. This improves data discoverability and empowers users to focus on the most relevant information. Arrange filters by frequency of use, with the most common filters appearing first. Examples include:
- Site picker: allow users to select a site
- Group picker: allow users to filter based on pre-defined groups of pages.
- Period picker: enable filtering by specific timeframes (e.g., date range, month, quarter).
Page title
const locale = useFormattingLanguage();
type SitesRequest = {
query: string;
pageNumber: number;
pageSize: number;
sortField: SortField<Site>;
};
const [sitesRequest, setSitesRequest] = useState<SitesRequest>({
query: "",
pageNumber: 1,
pageSize: 20,
sortField: { property: "isFavorite", direction: "asc" },
});
const { api: sitesApi } = useSitesDataAPI();
const sitesFetchDataFn = async (request: SitesRequest, signal?: AbortSignal) => {
// Simulate a delay to display the loading state.
await new Promise((resolve) => setTimeout(resolve, 2000));
return await sitesApi.getSites(request, signal);
};
const {
data: sitesData,
loading: loadingSites,
triggerRender: sitesTriggerRender,
} = useData(sitesFetchDataFn, sitesRequest);
const [selectedSite, setSelectedSite] = useState<Site>();
const loading = loadingSites && sitesData === null;
// Period Picker
const [periodPickerValue, setPeriodPickerValue] = useState<PeriodPickerValue | null>(null);
// Activity Plans
type ActivityPlan = {
isFavorite: boolean;
name: string;
searchEngineVisibility: number;
keywords: number;
competitors: number;
};
type ActivityPlansRequest = {
query: string;
page: number;
pageSize: number;
sortField: SortField<ActivityPlan>;
};
const [activityPlansRequest, setActivityPlansRequest] = useState<ActivityPlansRequest>({
query: "",
page: 1,
pageSize: 20,
sortField: { property: "isFavorite", direction: "asc" },
});
const { api: activityPlansApi } = useActivityPlansDataAPI();
const activityPlansFetchDataFn = async (request: ActivityPlansRequest, signal?: AbortSignal) =>
await activityPlansApi.getActivityPlans(request, signal);
const {
data: activityPlansData,
loading: loadingActivityPlans,
triggerRender: activityPlansTriggerRender,
} = useData(activityPlansFetchDataFn, activityPlansRequest);
const [selectedActivityPlan, setSelectedActivityPlan] = useState<ActivityPlan>();
return (
<BaseLayoutNew
id="content"
pageHeader={
<PageHeader
title="Page title"
utilities={{
global: (
<>
<Button variant="borderless" aria-label="Icon only button">
<Icon>
<IconSearch />
</Icon>
</Button>
<Button variant="borderless" aria-label="Icon only button">
<Icon>
<IconDownload />
</Icon>
</Button>
<Button variant="borderless" aria-label="Icon only button">
<Icon>
<IconSettings />
</Icon>
</Button>
<Button variant="borderless" aria-label="Icon only button">
<Icon>
<IconOptions />
</Icon>
</Button>
</>
),
productSpecific: (
<>
<Button variant="primary">Primary action</Button>
<Button variant="secondary">Secondary action</Button>
</>
),
}}
pickers={{
loading: loading,
items: [
sitesData && (
<SitePicker
items={sitesData.items}
sort={sitesRequest.sortField}
setSort={(property, direction) =>
setSitesRequest((prev) => ({
...prev,
sortField: {
property,
direction:
property === sitesRequest.sortField.property
? invertDirection(sitesRequest.sortField.direction)
: direction,
},
}))
}
search={{
query: sitesRequest.query,
onSearch: (query) => setSitesRequest((prev) => ({ ...prev, query })),
}}
onLoadMore={() =>
setSitesRequest((prev) => ({ ...prev, pageSize: prev.pageSize + 20 }))
}
totalItems={sitesData.totalItems}
selectedSite={selectedSite}
onSelectedSite={setSelectedSite}
onFavorite={async (site, isFavorite) => {
await sitesApi.updateFavoriteSite(site, isFavorite);
sitesTriggerRender();
}}
loading={loadingSites}
editSitesUrl="https://my2.siteimprove.com/Settings/Sites/v2"
extraColumns={[colPages(), colVisits()]}
/>
),
<PeriodPicker
key="period-picker"
translations={{
nextMonthAriaLabel: "Next month",
nextYearAriaLabel: "Next year",
nextCenturyAriaLabel: "Next century",
nextDecadeAriaLabel: "Next decade",
prevMonthAriaLabel: "Previous month",
prevYearAriaLabel: "Previous year",
prevCenturyAriaLabel: "Previous century",
prevDecadeAriaLabel: "Previous decade",
}}
periodButtonVariant="borderless"
periodButtonSize="large"
periodButtonStyle={{ minHeight: "initial" }}
value={periodPickerValue}
onChange={setPeriodPickerValue}
/>,
activityPlansData && (
<PageHeaderPicker
search={{
query: activityPlansRequest.query,
onSearch: (query) => setActivityPlansRequest((prev) => ({ ...prev, query })),
}}
onLoadMore={() =>
setActivityPlansRequest((prev) => ({ ...prev, pageSize: prev.pageSize + 20 }))
}
itemsCount={activityPlansData.items.length}
totalItems={activityPlansData.totalItems}
selectedItem={selectedActivityPlan}
popoverButtonIcon={<IconGoalTarget />}
selectedItemStringify={(item) => item.name}
loading={loadingActivityPlans}
texts={{
buttonContentNoItemSelected: "Select an Activity Plan",
searchPlaceholder: "Search for Activity Plans",
showingXOfYItems: (showing, total) =>
`Showing ${toFormattedNumberString({
number: showing,
locale,
})} of ${toFormattedNumberString({ number: total, locale })} Activity Plans`,
}}
toolbarActions={
<Button variant="primary" href="https://fancy.siteimprove.com/">
Edit Activity Plans
</Button>
}
contentItems={(
firstFocusableRef,
close,
tableClassName,
selectButtonClassName
) => (
<Table
items={activityPlansData.items}
columns={[
{
header: { content: "Favourite", property: "isFavorite" },
render: (item) => (
<Starred
isStarred={item.isFavorite}
onChange={async (starred) => {
await activityPlansApi.updateFavoriteActivityPlan(item, starred);
activityPlansTriggerRender();
}}
aria-label="Favorite item"
/>
),
},
{
header: { content: "Activity Plan", property: "name" },
render: (item, cellPosition) => (
<Button
variant="borderless"
ref={cellPosition.rowNum === 0 ? firstFocusableRef : undefined}
onClick={() => {
setSelectedActivityPlan(item);
close();
}}
className={selectButtonClassName}
>
{item.name}
</Button>
),
options: { isKeyColumn: true, cellPadding: "none" },
},
{
header: {
content: "Search engine visibility",
property: "searchEngineVisibility",
},
render: (item) => (
<FormattedNumber number={item.searchEngineVisibility} format="number" />
),
options: { align: "right" },
},
{
header: { content: "Keywords", property: "keywords" },
render: (item) => (
<FormattedNumber number={item.keywords} format="number" />
),
options: { align: "right" },
},
{
header: { content: "Competitors", property: "competitors" },
render: (item) => (
<FormattedNumber number={item.competitors} format="number" />
),
options: { align: "right" },
},
]}
sort={activityPlansRequest.sortField}
setSort={(property, direction) =>
setActivityPlansRequest((prev) => ({
...prev,
sortField: {
property,
direction:
property === activityPlansRequest.sortField.property
? invertDirection(activityPlansRequest.sortField.direction)
: direction,
},
}))
}
loading={false}
highlightRow={(item) => item.name === selectedActivityPlan?.name}
className={tableClassName}
/>
)}
/>
),
],
}}
/>
}
>
<>Page content 🦔</>
</BaseLayoutNew>
);
#Header with alert
Displays critical, system-wide messages that persist over the content area. Use header alerts sparingly and only for truly urgent messages to avoid information overload.
Page title
const locale = useFormattingLanguage();
// Period Picker
const [periodPickerValue, setPeriodPickerValue] = useState<PeriodPickerValue | null>(null);
const { ColorWhite } = useDesignToken();
return (
<BaseLayoutNew
id="content"
topHeaderArea={
<Message type="negative" style={{ borderRadius: "0" }}>
<Paragraph>
Some error message with a <Link href="https://fancylib.netlify.app">Link</Link>
</Paragraph>
</Message>
}
pageHeader={
<PageHeader
title="Page title"
breadcrumbs={{
"aria-label": "Breadcrumbs",
items: [
{ title: "Level 1", url: "https://fancylib.netlify.app" },
{ title: "Level 2", url: null },
],
}}
utilities={{
global: (
<>
<Button variant="borderless" aria-label="Icon only button">
<Icon>
<IconSearch />
</Icon>
</Button>
<Button variant="borderless" aria-label="Icon only button">
<Icon>
<IconDownload />
</Icon>
</Button>
<Button variant="borderless" aria-label="Icon only button">
<Icon>
<IconSettings />
</Icon>
</Button>
<Button variant="borderless" aria-label="Icon only button">
<Icon>
<IconOptions />
</Icon>
</Button>
</>
),
productSpecific: (
<>
<Button variant="primary">Primary action</Button>
<Button variant="secondary">Secondary action</Button>
</>
),
}}
pickers={{
loading: false,
items: [
<SitePicker
key="site-picker"
items={[]}
sort={{ property: "isFavorite", direction: "asc" }}
setSort={() => {}}
search={{ query: "", onSearch: () => {} }}
onLoadMore={() => {}}
totalItems={0}
selectedSite={undefined}
onSelectedSite={() => {}}
onFavorite={() => {}}
editSitesUrl="https://my2.siteimprove.com/Settings/Sites/v2"
/>,
<PageHeaderPicker
key="group-picker"
search={{ query: "", onSearch: () => {} }}
onLoadMore={() => {}}
itemsCount={0}
totalItems={0}
selectedItem={undefined}
popoverButtonIcon={<IconGroup />}
selectedItemStringify={() => "-"}
texts={{
buttonContentNoItemSelected: "Select a group",
searchPlaceholder: "Search for groups",
showingXOfYItems: (showing, total) =>
`Showing ${toFormattedNumberString({
number: showing,
locale,
})} of ${toFormattedNumberString({ number: total, locale })} groups`,
}}
toolbarActions={
<Button variant="primary" href="https://fancy.siteimprove.com/">
Edit groups
</Button>
}
contentItems={() => (
<EmptyState
type="reassure"
heading="No groups available"
style={{ backgroundColor: ColorWhite }}
/>
)}
/>,
<PeriodPicker
key="period-picker"
translations={{
nextMonthAriaLabel: "Next month",
nextYearAriaLabel: "Next year",
nextCenturyAriaLabel: "Next century",
nextDecadeAriaLabel: "Next decade",
prevMonthAriaLabel: "Previous month",
prevYearAriaLabel: "Previous year",
prevCenturyAriaLabel: "Previous century",
prevDecadeAriaLabel: "Previous decade",
}}
periodButtonVariant="borderless"
periodButtonSize="large"
periodButtonStyle={{ minHeight: "initial" }}
value={periodPickerValue}
onChange={setPeriodPickerValue}
/>,
<PageHeaderPicker
key="filter-picker"
search={{ query: "", onSearch: () => {} }}
onLoadMore={() => {}}
itemsCount={0}
totalItems={0}
selectedItem={undefined}
popoverButtonIcon={<IconFunnel />}
selectedItemStringify={() => "-"}
texts={{
buttonContentNoItemSelected: "Select a filter",
searchPlaceholder: "Search for filters",
showingXOfYItems: (showing, total) =>
`Showing ${toFormattedNumberString({
number: showing,
locale,
})} of ${toFormattedNumberString({ number: total, locale })} filters`,
}}
toolbarActions={
<Button variant="primary" href="https://fancy.siteimprove.com/">
Edit filters
</Button>
}
contentItems={() => (
<EmptyState
type="reassure"
heading="No filters available"
style={{ backgroundColor: ColorWhite }}
/>
)}
/>,
],
}}
/>
}
>
<>Page content 🦔</>
</BaseLayoutNew>
);
#Properties
Page title
Property | Description | Defined | Value |
---|---|---|---|
idRequired | string ID for the main section of base layout | ||
childrenOptional | element Elements to populate the base layout | ||
topHeaderAreaOptional | element Optional top header area | ||
pageHeaderOptional | element Optional page header element | ||
contentPaddingOptional | "medium" | "none" Padding for the content section |
#Guidelines
#Do's
#Don'ts
#Accessibility
Explore detailed guidelines for this component: Accessibility Specifications