MUST be used when a Flows/Fusion app needs full-screen "app-only" mode — hiding the Fusion sidebar and topbar so the app gets the whole viewport, and giving users a clear way to bring the shell back. Triggers: hideShell, full screen app, fullscreen mode, hide sidebar, hide topbar, hide shell, hide menu, setHideShell, app-only mode, kiosk mode, custom side nav, full viewport.
74
93%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Lets a Flows app hide the Fusion sidebar + topbar (the "shell") to use the full browser viewport, and reveal it again — without leaving the user stranded.
Requires @cognite/app-sdk's connectToHostApp() handshake already wired
up, and @cognite/app-sdk >= 0.9.0 (verify with npm ls @cognite/app-sdk
or check package.json — earlier versions don't expose setHideShell on
HostAppAPI at all). If auth isn't wired up yet, run the
setup-flows-auth skill first — but make sure
it lands on the Apps API flow (app.json has "infra": "appsApi",
deployed with npx @cognite/cli@latest apps deploy), not the Classic
flow (DuneAuthProvider/useDune() from @cognite/dune). setHideShell
only exists on the Apps API's HostAppAPI; Classic apps have no equivalent
and are deployed to infrastructure @cognite/cli itself refuses to touch
("Legacy infrastructure is no longer supported"). If app.json is missing
infra: "appsApi" or @cognite/dune shows up in package.json, stop and
migrate to Apps API first — don't attempt this skill on a Classic app.
HostAppAPI.setHideShell(hidden: boolean): Promise<void> (from the api
object returned by connectToHostApp()):
setHideShell(true) — hides the CDF sidebar and topbar, giving the app the
full viewport.setHideShell(false) — reveals them again.Under the hood this toggles a bookmarkable ?hideShell=true URL parameter —
no server round-trip, and a shared link already opens in full-screen mode.
The shell also auto-reveals if the user navigates away from your app, as
a safety net — but don't rely on that as your only way back.
There is no other safety net. While the shell is hidden, Fusion does not render any floating "reveal" button of its own — the navrail (and any toggle button inside it) is unmounted along with the rest of the shell. The only ways back are: your app's own reveal control, manually editing the URL, or navigating away entirely. Treat the guidance below as non-negotiable, not a nice-to-have.
Good fit:
Not a fit:
The #1 failure mode of this feature is trapping the user in full-screen with
no visible way to get the CDF navigation back. Every setHideShell(true)
call must ship with an equally discoverable reveal control:
aria-label (e.g. "Hide Cognite menu" /
"Show Cognite menu") — don't ship an icon button screen readers can't
interpret. A button with visible label text already has an accessible name
and doesn't need one.useHideShell hookCreate (or add to an existing hooks file) src/hooks/use-hide-shell.ts. This
centralizes the toggle logic and — critically — restores the shell on
unmount, so navigating within your own app (or an error boundary tearing
down the tree) can never leave the shell permanently hidden:
import { useCallback, useEffect, useState } from 'react';
import type { HostAppAPI } from '@cognite/app-sdk';
/**
* Manages Fusion shell visibility for full-screen "app-only" mode.
*
* Restores the shell automatically on unmount so it's never left hidden
* if the user navigates away or the component tears down unexpectedly.
*/
export function useHideShell(api: HostAppAPI | null) {
const [isHidden, setIsHidden] = useState(false);
const setHidden = useCallback(
async (next: boolean) => {
if (!api) return;
await api.setHideShell(next);
setIsHidden(next);
},
[api],
);
const toggle = useCallback(() => setHidden(!isHidden), [setHidden, isHidden]);
const hide = useCallback(() => setHidden(true), [setHidden]);
const reveal = useCallback(() => setHidden(false), [setHidden]);
useEffect(() => {
return () => {
if (isHidden && api) void api.setHideShell(false);
};
}, [api, isHidden]);
return { isHidden, toggle, hide, reveal };
}import type { HostAppAPI } from '@cognite/app-sdk';
import { Button } from '@cognite/aura/components';
import { IconEye, IconEyeOff } from '@tabler/icons-react';
import { useHideShell } from '../hooks/use-hide-shell';
function FullScreenToggle({ api }: { api: HostAppAPI | null }) {
const { isHidden, toggle } = useHideShell(api);
return (
<Button variant="secondary" size="sm" onClick={toggle} disabled={!api}>
{isHidden ? <IconEye aria-hidden /> : <IconEyeOff aria-hidden />}
{isHidden ? 'Show Cognite menu' : 'Hide Cognite menu'}
</Button>
);
}Place <FullScreenToggle api={api} /> wherever your best-practice placement
(above) calls for it — bottom of your custom nav, or a fixed corner control.
IconEye/IconEyeOff read more clearly as a visibility toggle than
arrows-style icons, which are easily confused with the separate native
Fullscreen API. This matches the icon choice used in a working, deployed
reference app for this skill (IconEyeOff to hide, IconEye to reveal).
Buttoncomes from the@cognite/aura/componentssubpath — the package only exports that path (plus./utils,./eslint,./styles.css), not a per-component@cognite/aura/components/buttonpath. Importing the latter throwsERR_PACKAGE_PATH_NOT_EXPORTEDat build time.
Add tests alongside the hook at src/hooks/use-hide-shell.test.ts:
import { act, renderHook } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { HostAppAPI } from '@cognite/app-sdk';
import { useHideShell } from './use-hide-shell';
function makeApi(): Pick<HostAppAPI, 'setHideShell'> {
return { setHideShell: vi.fn(() => Promise.resolve()) };
}
describe('useHideShell', () => {
let api: ReturnType<typeof makeApi>;
beforeEach(() => {
api = makeApi();
vi.clearAllMocks();
});
it('starts with the shell visible', () => {
const { result } = renderHook(() => useHideShell(api as HostAppAPI));
expect(result.current.isHidden).toBe(false);
});
it('hides the shell on toggle', async () => {
const { result } = renderHook(() => useHideShell(api as HostAppAPI));
await act(() => result.current.toggle());
expect(api.setHideShell).toHaveBeenCalledWith(true);
expect(result.current.isHidden).toBe(true);
});
it('reveals the shell on the second toggle', async () => {
const { result } = renderHook(() => useHideShell(api as HostAppAPI));
await act(() => result.current.toggle());
await act(() => result.current.toggle());
expect(api.setHideShell).toHaveBeenLastCalledWith(false);
expect(result.current.isHidden).toBe(false);
});
it('restores the shell on unmount when hidden', async () => {
const { result, unmount } = renderHook(() => useHideShell(api as HostAppAPI));
await act(() => result.current.hide());
unmount();
expect(api.setHideShell).toHaveBeenLastCalledWith(false);
});
it('does not call setHideShell on unmount when already visible', () => {
const { unmount } = renderHook(() => useHideShell(api as HostAppAPI));
unmount();
expect(api.setHideShell).not.toHaveBeenCalled();
});
it('is a no-op when api is null (running outside Fusion)', async () => {
const { result } = renderHook(() => useHideShell(null));
await act(() => result.current.toggle());
expect(result.current.isHidden).toBe(false);
});
});Three conditions must all be true for setHideShell(true) to actually
hide anything:
NAVIGATION_HIDE_SHELL Unleash flag is enabled for the environment.?hideShell present in the URL).Don't build extra fallback UI for the "flag off" case — the call is a no-op and the shell simply stays visible. Just don't assume the toggle always visibly does something in every environment while testing.
?hideShell=true.hideShell is removed from
the URL.?hideShell=true in the URL → shell starts hidden
(bookmarkable).If your own side nav needs to visually line up with (or replace) the CDF
sidebar, match these widths from apps/navigation's navrail
(apps/navigation/src/utils/constants.ts in cognitedata/fusion):
| State | Width |
|---|---|
| Expanded | min-width: 240px, max-width: 280px (actual width is content-driven, capped to that range) |
| Collapsed | 56px |
These are internal implementation details of the Fusion shell, not a public design-system token — re-check them if the sidebar's look changes noticeably, since there's no guarantee they stay in sync with this skill.
app-sdk
connection.c87160a
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.