Skip to Content

Shell

A component with a collapsible sidebar, theme and language toggles, and optional OAuth2 authentication.

Minimal Header Variant

Use the variant="minimal" prop to render a horizontal header layout instead of the default sidebar.

Features

  • Optional OAuth2 Authentication: Plug-and-play authorization code flow with PKCE
  • Theme Toggle: Built-in light/dark mode support
  • Language Toggle: Built-in language switcher for internationalization
  • User Profile: Dropdown menu with user information and sign-out

Installation

npx shadcn@latest add @uipath/shell

Usage

The shell works out of the box without authentication. Just provide layout props:

import { Home, Settings } from 'lucide-react'; import { ApolloShell } from '@/components/ui/shell'; const navItems = [ { path: '/dashboard', label: 'dashboard', icon: Home }, { path: '/settings', label: 'settings', icon: Settings }, ]; function App() { return ( <ApolloShell companyName="Your Company" productName="Your Product" navItems={navItems} > <YourApp /> </ApolloShell> ); }

Adding Authentication

To enable built-in OAuth2 authentication with PKCE, wrap the shell with ShellAuthProvider. Both ShellAuthProvider and ApolloShell must be inside a QueryClientProvider from @tanstack/react-query.

When a ShellAuthProvider is present, the shell automatically shows a login screen until the user is authenticated.

import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { Home, Settings } from 'lucide-react'; import { ApolloShell } from '@/components/ui/shell'; import { ShellAuthProvider } from '@/components/ui/shell-auth-provider'; const queryClient = new QueryClient(); const navItems = [ { path: '/dashboard', label: 'dashboard', icon: Home }, { path: '/settings', label: 'settings', icon: Settings }, ]; function App() { return ( <QueryClientProvider client={queryClient}> <ShellAuthProvider clientId="your-client-id" scope="openid profile email offline_access" baseUrl={window.location.origin} > <ApolloShell companyName="Your Company" productName="Your Product" navItems={navItems} > <YourApp /> </ApolloShell> </ShellAuthProvider> </QueryClientProvider> ); }

Gating access by group membership

Use GroupMembershipGuard to restrict the application to members of one or more UiPath Identity groups. The guard reads the live groups and groupMembers collections sourced by SolutionProvider from @uipath/vs-core, compares the signed-in user’s email against the combined member list, and renders either a loading indicator or an access-denied screen with a sign-out action. When the user is a member of any of the listed groups, the children render unchanged. An empty groupIds array denies access.

The toggle below switches between the loading, denied, and granted visual states using stubbed UI — the live data path is exercised in real apps via SolutionProvider.

Place GroupMembershipGuard inside both SolutionProvider (so it can read identity collections) and ShellAuthProvider (so it can read the authenticated user from useAuth):

import { SolutionProvider } from '@uipath/vs-core'; import { GroupMembershipGuard } from '@/components/ui/shell/group-membership-guard'; function App() { return ( <SolutionProvider orgName="your-org" tenantName="your-tenant" orgId="your-org-id" env="production" secret={solutionSecret} configuration={{ entities }} > <ShellAuthProvider clientId="your-client-id" scope="openid profile email offline_access" baseUrl={window.location.origin} > <GroupMembershipGuard groupIds={['admins-group-id', 'editors-group-id']} > <ApolloShell companyName="Your Company" productName="Your Product" navItems={navItems} > <YourApp /> </ApolloShell> </GroupMembershipGuard> </ShellAuthProvider> </SolutionProvider> ); }

Resolving group ids by role

GroupMembershipGuard and useIsGroupMember need group ids, but applications typically reason in terms of roles whose backing group names are known ahead of time. useGroupId looks up a group id by name from the groups collection exposed by SolutionProvider and re-renders reactively as the collection updates. It returns { groupId, isLoading }; groupId is null while the collection is loading or when no group with the configured name exists.

A default Role enum ships alongside the hook with two members — ADMIN and BUSINESS_USER. Replace it with your own string enum if you need different roles; the hook is generic over Role extends string and accepts any Record<Role, string> mapping role to group name.

import { GroupMembershipGuard } from '@/components/ui/shell/group-membership-guard'; import { Role } from '@/components/ui/shell/shell-roles'; import { useGroupId } from '@/components/ui/shell/use-group-id'; const ROLE_GROUP_MAP: Record<Role, string> = { [Role.ADMIN]: 'apollo-admins', [Role.BUSINESS_USER]: 'apollo-business-users', }; function AdminArea({ children }: { children: React.ReactNode }) { const { groupId, isLoading } = useGroupId({ role: Role.ADMIN, roleGroupMap: ROLE_GROUP_MAP, }); if (isLoading || !groupId) { return <div>Loading…</div>; } return ( <GroupMembershipGuard groupIds={[groupId]}>{children}</GroupMembershipGuard> ); }
Last updated on