Navigation
Glass Dock
A row of app icons on a glass shelf that name themselves on hover.
pnpm dlx shadcn@4.14.1 add @glassyui/glass-docknpx shadcn@4.14.1 add @glassyui-native/glass-dockPreview
Demos ready. Update 0.
This preview runs the React version. In an app, iOS 26 and later draw the React Native version with the system’s own Liquid Glass. See it on a phone.
Installation
First add the exact @glassyui mapping to components.json as shown in Getting Started. Then run the pinned item command:
pnpm dlx shadcn@4.14.1 add @glassyui/glass-dockWhat this installs10 files, 2 packages
Direct registry declarations
These values belong to @glassyui/glass-dock itself in registry.json.
Package dependencies
@radix-ui/react-slotclsx
Registry dependencies
@glassyui/glass-surface
File targets
components/glassyui/glass-dock.tsx
Complete installed closure
This follows every namespaced registry dependency recursively and de-duplicates the packages and target files installed with this item.
Package dependencies
@radix-ui/react-slotclsx
Installed files
components/glassyui/glass-dock.tsxcomponents/glassyui/glass-surface.tsxcomponents/glassyui/lib/merge-refs.tscomponents/glassyui/lib/refraction-map.tscomponents/glassyui/lib/use-refraction-filter.tsxcomponents/glassyui/lib/scene-context.tscomponents/glassyui/glass-provider.tsxcomponents/glassyui/liquid-filters.tsxcomponents/glassyui/lib/preferences.tscomponents/glassyui/styles/liquid.css
First add the @glassyui-native mapping to components.json as shown in the Expo guide. Then add the component and match its packages to your Expo SDK:
npx shadcn@4.14.1 add @glassyui-native/glass-docknpx expo install --fixUsage
Save this as dock-example.tsx. It is the complete example shown above, including its artwork.
'use client';
import { useId, useState, type CSSProperties } from 'react';
import { GlassDock, GlassDockItem } from '@/components/glassyui/glass-dock';
const apps = [
{ label: 'Inbox', from: '#407fe7', to: '#205ac4', path: 'M14 18h36v28H14z M14 19l18 14 18-14' },
{
label: 'Chat',
from: '#318e68',
to: '#1d654c',
path: 'M14 16h36v26H30l-11 8v-8h-5z M23 26h18 M23 33h12',
},
{
label: 'Gallery',
from: '#c9892f',
to: '#a76518',
path: 'M15 15h34v34H15z M15 40l10-10 9 9 6-6 9 10 M38 23h.1',
},
{ label: 'Player', from: '#ed603b', to: '#c9361a', path: 'M25 18v28l23-14z' },
{
label: 'Docs',
from: '#8e8e97',
to: '#686870',
path: 'M17 13h30v38H17z M24 23h16 M24 31h16 M24 39h11',
},
];
const dockStyle: CSSProperties & {
'--lg-dock-size': string;
'--lg-dock-gap': string;
'--lg-dock-pad': string;
} = {
'--lg-dock-size': 'clamp(32px, calc((100cqi - 32px) / 5), 52px)',
'--lg-dock-gap': 'clamp(4px, 1.4cqi, 8px)',
'--lg-dock-pad': 'clamp(6px, 1.4cqi, 10px)',
};
function DockAppIcon({ app }: { app: (typeof apps)[number] }) {
const id = useId();
return (
<svg aria-hidden="true" viewBox="0 0 64 64">
<defs>
<linearGradient id={id} x2=".35" y2="1">
<stop stopColor={app.from} />
<stop offset="1" stopColor={app.to} />
</linearGradient>
</defs>
<rect fill={`url(#${id})`} height="64" rx="14" width="64" />
<rect
x=".5"
y=".5"
width="63"
height="63"
rx="13.5"
fill="none"
stroke="#fff"
strokeOpacity=".2"
/>
<path
d={app.path}
fill="none"
stroke="#fff"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2.6"
/>
</svg>
);
}
type DockExampleProps = { onAction?: (message: string) => void };
export function DockExample({ onAction }: DockExampleProps) {
const [running, setRunning] = useState(['Chat']);
const [message, setMessage] = useState('');
return (
<div
style={{
containerType: 'inline-size',
display: 'grid',
alignContent: 'end',
justifyItems: 'center',
boxSizing: 'border-box',
width: '100%',
minHeight: 220,
paddingBlock: 24,
}}
>
<GlassDock aria-label="Dock" role="group" style={dockStyle}>
{apps.map((app) => (
<GlassDockItem
active={running.includes(app.label)}
key={app.label}
label={app.label}
onClick={() => {
setRunning((current) =>
current.includes(app.label) ? current : [...current, app.label],
);
const next = `${app.label} opened.`;
setMessage(next);
onAction?.(next);
}}
>
<DockAppIcon app={app} />
</GlassDockItem>
))}
</GlassDock>
{!onAction ? (
<span
role="status"
style={{
position: 'absolute',
width: 1,
height: 1,
overflow: 'hidden',
clipPath: 'inset(50%)',
}}
>
{message}
</span>
) : null}
</div>
);
}
After configuring the registry, install every component this example uses:
pnpm dlx shadcn@4.14.1 add @glassyui/glass-dockimport Ionicons from '@expo/vector-icons/Ionicons';
import { useState } from 'react';
import { GlassDock, GlassDockItem } from '@/components/glassyui/glass-dock';
import { useGlassTheme } from '@/components/glassyui/glass-provider';
const TABS = [
{ icon: 'home', label: 'Home' },
{ icon: 'calendar', label: 'Plans' },
{ icon: 'chatbubble', label: 'Messages' },
{ icon: 'person', label: 'Profile' },
] as const;
export default function DockExample() {
const { colors } = useGlassTheme();
const [tab, setTab] = useState<string>('Home');
return (
<GlassDock style={{ alignSelf: 'center' }}>
{TABS.map(({ icon, label }) => (
<GlassDockItem
active={tab === label}
key={label}
label={label}
onPress={() => setTab(label)}
>
<Ionicons color={colors.foreground} name={icon} size={24} />
</GlassDockItem>
))}
</GlassDock>
);
}
This example also uses Glass Provider. Install everything it uses:
npx shadcn@4.14.1 add @glassyui-native/glass-dock @glassyui-native/glass-providerExamples
import { DockExample } from "./dock-example";
export default function Page() {
return <DockExample />;
}API
The React Native version takes the same props under React Native’s names: onPress for onClick and style for className. The Expo guide lists what else differs.
GlassDockGlass shelf under a row of items.
GlassDockItemIcon button with a hover label and a running indicator.
labelRequired accessible name, also shown above the icon on hover.
activeShows the running dot under the icon.
asChildRenders the single child, such as a link, as the item.
GlassDockSeparatorVertical divider between item groups.
Notes
Input
Icons keep their size wherever the pointer is. Hover or keyboard focus shows the label, and focus also enlarges the icon.
Accessibility
Keep the Radix-backed semantics intact and preserve visible labels, focus states, and keyboard operation when copying the source.
Performance
Use provider quality preferences for dense app screens. Reserve strong refraction for hero or showcase surfaces.