PortalLogo
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 portal's white-label logo, fetched from the DocSpace server and swapped for the theme. It is
the mark at the top of the login and confirmation pages: one <img> pointed at the portal's
logo.ashx, with a bundled fallback when that request fails.
Use this when / not when
- Use on a page served by a DocSpace portal, where
/logo.ashxresolves and the administrator's white-label settings should be honoured. - Not in your own application — the URL is absolute from the site root and has no prop to change
it, so outside a portal it 404s and you always get the bundled fallback. Render your own
<img>instead. - Not for a room's mark — use
RoomLogoorRoomIcon.
Portal-internal. The source is /logo.ashx?logotype=…&dark=…&default=false, a DocSpace
endpoint. There is no src prop, no base-URL prop and no way to point it elsewhere.
Import
import { PortalLogo } from "@onlyoffice/apps-ui-kit/components/portal-logo";
Also exported from the root barrel @onlyoffice/apps-ui-kit.
The component file has a default export, but the folder's index re-exports it under a name —
import PortalLogo from … does not resolve.
Needs ThemeProvider above it in the tree, and it reads the theme in JavaScript: isBase
becomes the dark query parameter, so the request itself differs between themes. Without a
provider the context falls back to light and the dark logo is never requested.
Stories
Default
The logo as a wide screen shows it, at its full size. No portal serves the image here, so a placeholder stands in for it; change any other prop live in the Controls panel below.
Resizable
The same logo, now following the window width: narrow the window to 600px or less and it moves into a bar fixed across the top (isResizable). The OnPhone story shows that layout.
With Class Name
Styling the logo from outside: the class lands on the image itself, not on the wrapper around it (className).
On Phone
On a phone the logo moves into a 48px bar fixed across the top of the window, at a smaller size (isResizable). Without isResizable nothing is shown at this width.
Fallback Logo
What a page shows when the portal's logo cannot be loaded: the bundled logo takes the image's place, at its own size, so the page never has an empty gap or a broken-image icon.
Css Customization
One resizable logo with every variable set on its wrapper -- the variables are listed under CSS variables on this page. On a wide screen it shows the two desktop variables; narrow the window to 600px or less to see the three bar variables, with the desktop width still applied to the logo inside the bar.
Minimal example
import { PortalLogo } from "@onlyoffice/apps-ui-kit/components/portal-logo";
export function LoginHeader() {
return <PortalLogo />;
}
Props
Recipes
The logo that survives small screens
Without isResizable the logo is hidden at 600px and narrower. With it, the wrapper becomes a fixed bar
across the top of the viewport, 48px tall, with the small logo centred in it — which means it
overlays your layout rather than sitting in it, and the page needs 48px of its own padding.
import { PortalLogo } from "@onlyoffice/apps-ui-kit/components/portal-logo";
export function ConfirmPage({ children }: { children: React.ReactNode }) {
return (
<div style={{ paddingTop: 48 }}>
<PortalLogo isResizable />
{children}
</div>
);
}
Sizing it
The desktop image is forced to 386×44 by the component's own rule, and className lands on the
<img>, so your own width and height need to win on specificity — or go through the two custom
properties below, which is what they are there for.
import { PortalLogo } from "@onlyoffice/apps-ui-kit/components/portal-logo";
// In your stylesheet:
// .login-page { --portal-logo-desktop-img-width: 240px;
// --portal-logo-desktop-img-height: 28px; }
export function SmallerLogo() {
return (
<div className="login-page">
<PortalLogo />
</div>
);
}
Behaviour the types don't state
- At 600px and narrower a logo without
isResizableisdisplay: none. Not smaller — gone. This is the single most surprising thing about the component. - The width is read once, at first render. The resize listener is attached only while
isResizableis set, so without it the component's idea of the viewport never changes after mount. During server rendering the width starts at0, which counts as mobile. - A failed request replaces everything. The
onErrorhandler swaps the whole component for a bundled small logo — the wrapper and its layout classes included, so the fixed mobile bar disappears with it.classNamestill reaches that fallback. - Two separate ideas of "mobile" are in play: the measured window width decides the layout,
while
react-device-detect's user-agent check decides thenot-mobileclass that carries the desktop 386×44 sizing. A narrow window on a desktop browser gets the desktop size rule and the mobile layout at the same time. classNamegoes on the image, never on the wrapper, so a rule that needs to position or space the logo has to target the wrapper through a parent of yours.- The image's
altis the fixed English string"portal logo", untranslated and not settable. - There is no
data-testidanywhere. Tests have to go throughgetByRole("img")or thelogo-wrapperclass.
CSS variables
The three mobile variables apply only to the fixed bar, so only with isResizable at 600px and
narrower. The two desktop ones hang on the not-mobile class, which follows the user-agent check
rather than the width: a phone browser never reads them, while a narrow desktop window does. In
that case the bar's --portal-logo-mobile-img-height wins over the desktop height, but
--portal-logo-desktop-img-width still sizes the logo inside the bar.
Accessibility
- The logo is an
<img>withalt="portal logo". That string is not translated, and on a login page where the logo is the only statement of which portal you are on, it is also not very informative — there is no prop to improve it. - Nothing here is focusable or interactive; the logo is not a link.
- In the resizable mobile layout the bar is
position: fixedat the top of the viewport, so it sits above the page content and can cover a focused element unless the page reserves space.
Test ids
The component sets none. Select it by role="img", or by the logo-wrapper class it puts on
the image.