跳到主要内容

Section

Portal only

Works only inside an ONLYOFFICE Apps portal: it needs the portal's API client, stores and translations, and is not part of the public API.

The ONLYOFFICE Apps page body: a sticky header and filter, a scrolling body, and the info and chat panels beside it. It is the other half of the portal's layout, next to Article, and it is also what supplies the kit's layout context to everything inside it.

SectionSection

Use this when / not when​

  • This is portal-internal. Nearly ninety props, most of them wiring for the portal's upload and operations machinery, and about a quarter of them dead.
  • Use it when you are rebuilding the ONLYOFFICE Apps page layout and want the same header, filter, info panel and scroll behaviour.
  • Several kit components need to be inside one. It is the only source of the layout context sectionWidth and sectionHeight, which Navigation's breadcrumb box reads, and it renders the sectionScroll element that SelectionArea needs.
  • Not for an ordinary page shell — a <main> of your own with the kit's components inside it costs nothing and behaves predictably.
  • It is not a container. Its children are ten marker components whose contents it lifts out; with no header, filter or body among them it renders null.

Import​

import Section from "@onlyoffice/apps-ui-kit/components/section";

It is a default export, so the name is yours to choose. The root barrel carries it by name as well — components/index.ts re-exports it as export { default as Section } — but prefer the subpath: the barrel does not build without four optional peers, see Which import form.

Needs ThemeProvider above it in the tree for its colours, and TranslationProvider once any operation is in flight — the progress button it renders reads its labels from the kit's shared translations and shows empty strings without one.

Stories​

Default​

A whole page: breadcrumbs in the header, a search and filter bar under it and a table in the body, each outlined and labelled so you can see where the section puts its slots. Scroll the table to see the header and the filter stay pinned; change any other prop live in the Controls panel below.

DefaultDefault

With Info Panel​

A details panel beside the listing, for the properties of the selected item without leaving the page. It shows only while both canDisplay and isInfoPanelVisible are on; switch either off in the Controls panel below to close it.

With Info PanelWith Info Panel

With Chat Panel​

A chat docked beside the listing, so a conversation about the files stays open while you work with them. Drag its inner edge to make it wider or narrower (isChatPanelResizable); the new width arrives when you release the mouse (setChatPanelWidth).

With Chat PanelWith Chat Panel

With Banner​

A notice above the header that stays in view while the table scrolls (Section.SectionBanner). Switch scrollableBanner on in the Controls panel below to put it at the top of the listing instead, where it scrolls away under the header.

With BannerWith Banner

With Submenu​

Tabs under the header that switch between views of the same page and stay pinned with it while the listing scrolls (Section.SectionSubmenu).

With SubmenuWith Submenu

With Operations Progress​

A round progress button in the bottom corner, so a long copy or upload stays visible while the user goes on working (secondaryActiveOperations). Hover it to read what is running; an empty list hides it again.

With Operations ProgressWith Operations Progress

With Context Menu​

A menu of page-wide actions on a right click anywhere in the body, for what applies to the folder rather than to one file (getContextModel). Right click the list to open it.

With Context MenuWith Context Menu

On Tablet​

The page in a tablet-width window: the header stays pinned, while the filter bar moves into the listing and scrolls with it, leaving more room for the rows (currentDeviceType).

On TabletOn Tablet

On Phone​

The page on a phone: the header and the filter bar both move into the listing and the whole page scrolls, so the rows get the full height of the screen (currentDeviceType).

On PhoneOn Phone

Right To Left​

The page in a right-to-left interface: the info panel opens on the left, its border moves to its right edge, and the table's columns run from the right.

Right To LeftRight To Left

Css Customization​

The variables are listed under CSS variables on this page. The example sets every desktop one on a wrapper around one section with both panels open: the blue strip is the pinned header, the pale blue column is the info panel and the yellow one is the chat panel, with its drop frame on. Navigation, Filter and the table have variables of their own, documented in their stories and set on the same wrapper.

Css CustomizationCss Customization

Minimal example​

import Section from "@onlyoffice/apps-ui-kit/components/section";
import { Text } from "@onlyoffice/apps-ui-kit/components/text";
import { DeviceType } from "@onlyoffice/apps-ui-kit/enums";

export function Page() {
return (
<div style={{ display: "flex", height: "100vh" }}>
<Section
withBodyScroll
settingsStudio={false}
currentDeviceType={DeviceType.desktop}
>
<Section.SectionHeader>
<Text fontSize="18px" fontWeight={700}>
Contracts
</Text>
</Section.SectionHeader>
<Section.SectionBody>
<Text>Two files</Text>
</Section.SectionBody>
</Section>
</div>
);
}

Props​

PropertyTypeDescription
childrenReactNodeRequired. The slots. Each child must be one of the ten Section.* markers; the section reads their contents and renders them in its own places, and drops everything else. With no header, filter or body slot it renders nothing at all.
settingsStudiobooleanRequired. Applies the settings pages' narrower body padding. Default: false.
withBodyScrollbooleanRequired. Whether the section scrolls its own body. With false the page scrolls instead and the section takes a 20px inline-start padding. Default: true.
aiChatIDstringNot read. Nothing in the component destructures it.
aiChatIsVisiblebooleanNot read. Nothing in the component destructures it.
aiSelectedFoldernumber | stringNot read. Nothing in the component destructures it.
aiUserIdstringNot read. Nothing in the component destructures it.
anotherDialogOpenbooleanSuppresses the info panel below the desktop breakpoint while a dialog of yours is open, so the two do not stack.
asideInfoPanelbooleanNot read. The info panel reads topInfoPanel, which the section never passes on.
bannerContentReactNodeNot read by the section: the banner comes from the SectionBanner slot instead.
cancelSecondaryOperationById(operation: string, operationId: string) => voidCancels one background operation by id.
cancelUpload() => voidCalled by the progress button's cancel control while an upload is running.
canDisplaybooleanThe info panel is not rendered without this. isInfoPanelVisible alone is not enough: both must be true.
chatFiles(TFile | TFolder)[]Not read. Nothing in the component destructures it.
chatPanelDropTargetLabelstringOverlay label shown while a drag started in the section body hovers the chat panel; leave unset the rest of the time. The host owns both the hit-testing (its list drag is mouse-based, not HTML5 DnD) and the wording — the panel only renders the affordance.
chatPanelWidthnumberCurrent width of the docked chat panel in pixels.
clearDropPreviewLocation() => voidClears that drop preview.
clearPrimaryProgressData(operation?: string | null) => voidClears a finished upload operation.
clearSecondaryProgressData(operationId?: string | null, operation?: string | null, operationItem?: Operation) => voidClears a finished background operation.
currentDeviceTypeDeviceTypeWhich layout to render. It decides where the header and the filter go, whether the body scrolls itself, and whether the info panel is inline or a portal over the page. Nothing here measures the viewport.
displayFileExtensionbooleanNot read. Nothing in the component destructures it.
draggingbooleanWhether a drag of the host's own items is in progress, which the progress button uses for its drop-preview state.
dropTargetPreviewstringName of the folder a dragged item would land in, shown by the progress button.
fullHeightBodybooleanMakes the body fill the section's height rather than its content's, for a chat-like page whose inner regions scroll instead.
getContextModel() => ContextMenuModel[]Returns the model for the body's own right-click menu. Without it no menu is mounted; it is also suppressed while isIndexEditingMode is set.
getIcon(size: number, fileExst: string) => stringNot read. Nothing in the component destructures it.
inertbooleanMarks the section root (#section) and its whole subtree as inert — non-focusable and non-interactive. Used when the section is visually collapsed but kept mounted (e.g. behind the fullscreen AI chat panel).
infoPanelWithoutScrollbooleanRemoves the info panel body's scroller and the aside's, for a body that scrolls its own regions.
isChatPanelAvailablebooleanWhether the AI chat region exists at all. It defaults to false. Default: false.
isChatPanelFullscreenbooleanWhether the host renders the chat panel fullscreen right now.
isChatPanelResizablebooleanOpt-in edge resizer for the docked chat panel (desktop only). Pass the width the host stores plus a setter; leave unset for a fixed-width panel.
isChatPanelVisiblebooleanWhether the AI chat panel is open.
isDesktopbooleanNot read by the section: it is never forwarded to the body.
isEmptyPagebooleanNot read. Nothing in the component destructures it.
isHeaderVisiblebooleanNot read. Nothing in the component destructures it.
isIndexEditingModebooleanSuppresses the body's right-click menu while the listing is being reordered.
isInfoPanelAvailablebooleanWhether the info panel region exists at all. It defaults to true, so the panel's markup is mounted unless you turn it off. Default: true.
isInfoPanelScrollLockedbooleanFreezes the info panel's own scroller, for a drag or a menu that must not scroll the panel under it.
isInfoPanelVisiblebooleanWhether the info panel is open. It narrows the section and is one of the two conditions for the panel being rendered at all — canDisplay is the other.
isMobileHiddenbooleanHides the info panel on anything narrower than a desktop, where it would otherwise cover the page.
isTabletViewbooleanNot read. Nothing in the component destructures it.
isTrashFolderbooleanNot read. Nothing in the component destructures it.
mainBarVisiblebooleanNot read. Nothing in the component destructures it.
mainButtonVisiblebooleanWhether the main button is on screen, which moves the progress button clear of it.
maintenanceExistbooleanNot read. Nothing in the component destructures it.
needErrorCheckingbooleanMakes the progress button check its operations for errors before reporting them complete.
onCancelOperation(callback: () => void) => voidNot read. Nothing in the component destructures it.
onClose() => voidNot read by the section: it is never forwarded to the info panel.
onDragLeaveEmpty() => voidCalled when a drag leaves the body.
onDragOverEmpty(isDragActive: boolean) => voidCalled on every drag-over of the body, with a drag-active flag that is one render behind.
onDropTOnDropCalled with the files dropped anywhere on the body, which is itself a drop target. Nothing is filtered and nothing is highlighted — see DragAndDrop.
onOpenUploadPanel() => voidCalled when the operations button asks to open the upload panel.
pathnamestringThe current route. Changing it re-focuses the body on a desktop, which is how the portal restores keyboard scrolling after a navigation.
pluginOperationsOperation[]Operations contributed by plugins, merged into the background operations of the progress button. Default: [].
pluginOperationsAlertbooleanPuts the progress button in its alert state for a failed plugin operation.
pluginOperationsCompletedbooleanWhether those plugin operations have finished.
pluginShowCancelButtonbooleanForces the progress button's cancel control on, whatever the operations say.
primaryOperationsAlertbooleanPuts the progress button in its alert state for a failed upload.
primaryOperationsArrayOperation[]Upload operations shown in the progress button's own panel. Default: [].
primaryOperationsCanceledbooleanWhether the uploads were cancelled.
primaryOperationsCompletedbooleanWhether the panel operations — uploads — have finished.
progressBarDropDownContentReactNodeNot read. Nothing in the component destructures it.
refRefObject<HTMLDivElement | null>Not read by the section: it keeps its own ref on the container.
scrollableBannerbooleanWhen true, the banner is rendered inside the scrollable body (as its first element) instead of being pinned above the scroll container. Lets the banner scroll away under the sticky section header. Defaults to false to preserve the legacy pinned-banner behaviour.
secondaryActiveOperationsOperation[]Background operations shown in the progress button. Its length is one of the three that decide whether the button appears. Default: [].
secondaryOperationsAlertbooleanPuts the progress button in its alert state for a failed background operation.
secondaryOperationsCompletedbooleanWhether the background operations have finished, for the progress button's completed state.
secondaryOperationsStoppedbooleanWhether the background operations were stopped rather than finished.
setAiChatIsVisible() => voidNot read. Nothing in the component destructures it.
setChatPanelFullscreen() => voidTurns the chat panel fullscreen on. Called when the edge resizer is dragged past the widest docked width; leave unset to keep that drag clamped at the limit.
setChatPanelWidth(value: number) => voidCalled once per resize drag, on mouse up, with the committed width.
setIsChatPanelVisible(value: boolean) => voidCalled by the chat panel's own close control.
setIsInfoPanelVisible(value: boolean) => voidCalled with false when the info panel closes itself — a click beside it, or the browser going back below the desktop breakpoint.
showTextbooleanNot read. Nothing in the component destructures it.
snackbarExistbooleanNot read. Nothing in the component destructures it.
startDropPreview() => voidIts mere presence makes the progress button appear, even with no operations, so that a drag can show its drop preview.
stickyTableHeaderbooleanWhen true, the desktop SectionFilter slot is rendered INSIDE the scroll body as a sticky element (below the optional scrollable banner) instead of the always-pinned .section-sticky-container, and the table header is switched from position: fixed to position: sticky so it pins below the in-body filter natively (no host JS to measure the header top). The host supplies the pin offset via the --section-filter-bottom CSS variable. Defaults to false (legacy fixed-header + sticky-container-filter).
topInfoPanelbooleanNot read by the section: it is never forwarded to the info panel.
unsetChatPanelFullscreen() => voidTurns the chat panel fullscreen off. Called when the resizer is dragged back inwards in fullscreen, which then continues as a normal resize.
uploadFilesbooleanNot read. Nothing in the body destructures it.
userTUserNot read. Nothing in the component destructures it.
vectorizedFilesTFile[]Not read. Nothing in the component destructures it.
viewAsTViewAsWhich listing the body holds. It changes the body's padding, and in row view it lets a click beside the info panel close it.
withoutFooterbooleanDrops both the footer slot and the spacer under the body. Default: false.
withoutScrollbooleanNot read at this level. The component passes infoPanelWithoutScroll here instead.
withTabsbooleanTells the filter row that tabs sit above it, which removes its top margin. Honoured below the desktop breakpoint, where the filter lives inside the body.

Recipes​

The info panel​

Two props, not one. isInfoPanelVisible opens it and canDisplay permits it — and canDisplay has no default, so the panel stays invisible until you pass it.

import { useState } from "react";

import Section from "@onlyoffice/apps-ui-kit/components/section";
import { Text } from "@onlyoffice/apps-ui-kit/components/text";
import { Button } from "@onlyoffice/apps-ui-kit/components/button";
import { DeviceType } from "@onlyoffice/apps-ui-kit/enums";

export function PageWithInfoPanel() {
const [visible, setVisible] = useState(false);

return (
<div style={{ display: "flex", height: "100vh" }}>
<Section
withBodyScroll
settingsStudio={false}
currentDeviceType={DeviceType.desktop}
canDisplay
isInfoPanelVisible={visible}
setIsInfoPanelVisible={setVisible}
>
<Section.SectionHeader>
<Text fontSize="18px" fontWeight={700}>
Contracts
</Text>
</Section.SectionHeader>
<Section.SectionBody>
<Button
label={visible ? "Hide details" : "Show details"}
onClick={() => setVisible((value) => !value)}
/>
</Section.SectionBody>
<Section.InfoPanelHeader>
<Text fontWeight={600}>Details</Text>
</Section.InfoPanelHeader>
<Section.InfoPanelBody>
<Text>Report.docx — 42 KB</Text>
</Section.InfoPanelBody>
</Section>
</div>
);
}

Disabled / read-only​

inert marks the section's whole subtree inert: nothing inside can be focused, clicked or reached by a screen reader. The portal uses it while the chat panel is fullscreen over the page.

import Section from "@onlyoffice/apps-ui-kit/components/section";
import { Text } from "@onlyoffice/apps-ui-kit/components/text";
import { DeviceType } from "@onlyoffice/apps-ui-kit/enums";

export function FrozenPage({ frozen }: { frozen: boolean }) {
return (
<div style={{ display: "flex", height: "100vh" }}>
<Section
inert={frozen}
withBodyScroll
settingsStudio={false}
currentDeviceType={DeviceType.desktop}
>
<Section.SectionHeader>
<Text fontSize="18px" fontWeight={700}>
Contracts
</Text>
</Section.SectionHeader>
<Section.SectionBody>
<Text>Nothing here can be reached while it is frozen.</Text>
</Section.SectionBody>
</Section>
</div>
);
}

Where the filter goes​

The filter slot moves with currentDeviceType: pinned above the scroller on a desktop, inside the body below it. stickyTableHeader moves the desktop one into the body as well, where it sticks natively and a table header can pin under it.

import Section from "@onlyoffice/apps-ui-kit/components/section";
import { Text } from "@onlyoffice/apps-ui-kit/components/text";
import { SearchInput } from "@onlyoffice/apps-ui-kit/components/search-input";
import { InputSize } from "@onlyoffice/apps-ui-kit/components/text-input";
import { DeviceType } from "@onlyoffice/apps-ui-kit/enums";

export function FilteredPage() {
return (
<div style={{ display: "flex", height: "100vh" }}>
<Section
withBodyScroll
settingsStudio={false}
currentDeviceType={DeviceType.desktop}
stickyTableHeader
>
<Section.SectionHeader>
<Text fontSize="18px" fontWeight={700}>
Contracts
</Text>
</Section.SectionHeader>
<Section.SectionFilter>
<SearchInput value="" size={InputSize.base} onChange={() => {}} />
</Section.SectionFilter>
<Section.SectionBody>
<Text>Two files</Text>
</Section.SectionBody>
</Section>
</div>
);
}

Behaviour the types don't state​

  • With no header, filter or body slot it renders nothing. A section holding only an info panel or only a footer returns null, panels included.
  • The slots are markers that render nothing themselves, matched by display name while the section renders. Wrapping one in a component of your own, or memoising it, hides its content.
  • The info panel needs canDisplay as well as isInfoPanelVisible, and canDisplay is optional with no default — the single commonest reason the panel does not appear.
  • It is the only source of the kit's layout context. sectionWidth and sectionHeight are measured from the container with a ResizeObserver and a 100ms debounce and published to everything inside; components that read them fall back to zero elsewhere.
  • It renders the element SelectionArea requires. With withBodyScroll set and outside a phone layout, the body's scroller carries id="sectionScroll"; the container itself is id="section".
  • It dispatches a synthetic resize event on the window whenever it measures itself, which is on mount and whenever the info or chat panel is toggled.
  • The body is a drop target. It is a DragAndDrop, so onDrop receives every file dropped anywhere on the page body, nothing is filtered, and nothing is highlighted unless you draw it.
  • On a desktop the body takes focus and keeps it. It focuses itself on mount and on every pathname change, and refocuses whenever focus leaves the document altogether. It also removes the tabindex from an element with the literal id customScrollBar while it is mounted.
  • The info panel assigns window.onpopstate directly, replacing any handler already there, and never restores it.
  • About twenty-five props are dead, among them user, getIcon, showText, isEmptyPage, maintenanceExist, snackbarExist, isTrashFolder, mainBarVisible, every aiChat* prop except the panel's own, bannerContent, topInfoPanel, onClose, asideInfoPanel, isDesktop, uploadFiles and a ref you pass. The table above says so prop by prop.
  • withBodyScroll and settingsStudio have destructuring defaults but are declared required, so the compiler asks for them anyway.
  • The header's height and background come from the theme, not from a prop. 69px on a desktop, 61px on a tablet, 53px on a phone, and white or black to match the light/dark class on <body>. Override any of them with the custom properties below rather than by styling the header element.
  • It is memoised with a deep equality check over all its props, arrays included, which is expensive on long operation lists and means an identity change alone does not re-render it.
  • The progress button appears as soon as any operation array is non-empty or startDropPreview is passed — the handler's presence alone is enough.

Sub-components​

NameWhere its content is rendered
Section.SectionHeader
The sticky header; moved inside the scrolling body on a phone
Section.SectionSubmenu
Under the header, or inside the body on a phone
Section.SectionFilter
Under the submenu on a desktop, inside the body otherwise — or always inside the body with stickyTableHeader
Section.SectionBanner
Pinned above the scroller, or as the body's first element with scrollableBanner
Section.SectionWarning
Above the body's content, below the desktop breakpoint only
Section.SectionBody
The scrolling body
Section.SectionFooter
Under the body, unless withoutFooter
Section.InfoPanelHeader
The info panel's header
Section.InfoPanelBody
The info panel's body
Section.ChatPanel
The AI chat panel, rendered only while isChatPanelAvailable

They are static properties of the exported component, they take nothing but children, and they render nothing themselves.

CSS variables​

VariableDefaultEffect
--section-bgthemeBackground of the pinned strip that holds the header, the submenu and the desktop filter
--section-header-size69pxHeight of the header on a desktop
--section-header-tablet-size61pxHeight of the header on a tablet
--section-header-mobile-size53pxHeight of the header on a phone
--section-filter-top0With stickyTableHeader: where the filter comes to rest under the header
--section-filter-height0With stickyTableHeader: minimum height of the filter row
--section-filter-bottom0With stickyTableHeader: where the table header comes to rest under the filter
--info-panel-width400pxWidth of the info panel on a desktop
--info-panel-tablet-width480pxWidth of the info panel on a tablet, capped at the window width less 69px; on a phone the panel is always full width
--info-panel-backgroundthemeBackground of the info panel
--info-panel-border-colorthemeIts inline-start border; desktop only, the panel has no border below it
--info-panel-backdropthemeColour of the overlay behind it below the desktop breakpoint
--chat-panel-width400pxWidth of the docked chat panel
--chat-panel-backgroundthemeBackground of the chat panel
--chat-panel-border-colorthemeIts inline-start border
--chat-panel-drop-overlay-backgroundthe chat panel's ownFill of its "drop to attach" frame, drawn at 85% opacity
--chat-panel-drop-border-coloraccent; white in darkDashed border of that frame
--chat-panel-drop-inset-top69pxSpace left above that frame for the chat's own header; a phone always leaves 53px, whatever you set

Every default above is the theme's own value, applied through the light/dark class on <body>; the table lists what you get when you override nothing.

Below the desktop breakpoint the info panel is portalled to #root, out of any wrapper around the section, so its variables have to be set on :root (or on an ancestor of #root) to reach it there. A resizable chat panel owns its width: with isChatPanelResizable on a desktop, docked and with chatPanelWidth set, the panel writes --chat-panel-width on itself, which outranks a value set on a wrapper. --section-content-padding does nothing: it is read only on the header, and the body's negative margin never sees it.

Accessibility​

  • The section is not a landmark. It is a <div id="section">, not a <main>, with no role and no label, so the page has no main region unless you supply one around it.
  • On a desktop the body grabs focus on mount and takes it back whenever focus leaves the document, which overrides where a keyboard user had put it.
  • inert is the one honest state here: it marks the whole subtree non-focusable and hides it from assistive technology.
  • The info panel is a plain region even when it covers the page below the desktop breakpoint: focus is not moved into it, not trapped, and not restored when it closes, and Escape does not close it.
  • PageUp, PageDown, Home and End pressed inside the info panel's scroller stop there, so they scroll the panel and not the listing behind it.
  • The chat panel's resize handle is mouse-only and carries role="presentation", so it cannot be reached from the keyboard and screen readers skip it.
  • Nothing announces the operations progress; the button that shows it is visual only.

Test ids​

The component sets none, on any element. Select on the container's id section, on the scroller's id sectionScroll, or on the stable classes section-header, section-body, section-banner and info-panel.

  • Article — the panel beside it, and the other half of the layout.
  • Navigation — what normally fills the header slot.
  • SelectionArea — needs the scroller this renders.