Patterns · Original
Glass dock
A glass navigation dock over an original saturated landscape with selected and magnified destinations.
Live preview
Try it in context
Customize preview
Source delivery
v0.3.0Usage & source
"use client";
import type { CSSProperties } from "react";
import { GlassDock } from "./components/stepkit/GlassDock";
import "./components/stepkit/styles.css";
import "./components/stepkit/showcase.css";
const themeStyle = {"--stepkit-bg":"#111217","--stepkit-surface":"#1b1c24","--stepkit-surface-muted":"#22232d","--stepkit-fg":"#f6f5fb","--stepkit-muted":"#a4a6b4","--stepkit-border":"#343641","--stepkit-border-strong":"#565866","--stepkit-code":"#20212a","--stepkit-shadow":"0 24px 70px rgba(0, 0, 0, .34)","--stepkit-accent":"#057052","--stepkit-accent-strong":"#034b38","--stepkit-accent-soft":"#153f35","--stepkit-accent-on-soft":"#63d4b2","--stepkit-accent-text":"#ffffff","--stepkit-error":"#ff9ba9","--stepkit-placeholder":"#b8bac6","--stepkit-radius":"28px","--stepkit-space":"16px","--stepkit-control-height":"44px","--stepkit-font-sans":"Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif"} as CSSProperties;
export default function Example() {
return (
<div className="stepkit-theme" data-stepkit-preset="modern" style={themeStyle}>
<GlassDock
headline={"Find your flow."}
paused={false}
/>
</div>
);
}Dependencies
[email protected] [email protected]
/*
* Stepkit 0.3.0
* Copyright (c) 2026 Vladislav Stepanov
*
* Original Stepkit portions in this release are licensed under the MIT License. Adapted portions identify their upstream licence and attribution in the copied source and in the entry-specific upstream notice files.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
"use client";
import { useState, type ReactNode } from "react";
import "./styles.css";
import "./showcase.css";
export type GlassDockIcon = "grid" | "spark" | "layers" | "chart" | "user";
export interface GlassDockItem { id: string; label: string; icon?: GlassDockIcon; badge?: boolean; }
export interface GlassDockProps {
items?: GlassDockItem[];
selectedId?: string;
defaultSelectedId?: string;
onSelect?: (item: GlassDockItem) => void;
headline?: string;
description?: string;
paused?: boolean;
children?: ReactNode;
className?: string;
}
const defaultItems: GlassDockItem[] = [
{ id: "canvas", label: "Canvas", icon: "grid" },
{ id: "spark", label: "Spark", icon: "spark", badge: true },
{ id: "layers", label: "Layers", icon: "layers" },
{ id: "analytics", label: "Analytics", icon: "chart" },
{ id: "profile", label: "Profile", icon: "user" }
];
function DockIcon({ icon = "grid" }: { icon?: GlassDockIcon }) {
if (icon === "spark") return <svg viewBox="0 0 24 24" aria-hidden="true"><path d="m12 2 1.8 6.2L20 10l-6.2 1.8L12 18l-1.8-6.2L4 10l6.2-1.8L12 2Zm7 13 .7 2.3L22 18l-2.3.7L19 21l-.7-2.3L16 18l2.3-.7L19 15Z" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.6" /></svg>;
if (icon === "layers") return <svg viewBox="0 0 24 24" aria-hidden="true"><path d="m12 3 8 4-8 4-8-4 8-4Zm-8 9 8 4 8-4M4 17l8 4 8-4" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.7" /></svg>;
if (icon === "chart") return <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 19V5m0 14h16M8 15l3-4 3 2 4-6" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.7" /></svg>;
if (icon === "user") return <svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="8" fill="none" r="3.5" stroke="currentColor" strokeWidth="1.7" /><path d="M5 20c.8-3.5 3.2-5.2 7-5.2s6.2 1.7 7 5.2" fill="none" stroke="currentColor" strokeLinecap="round" strokeWidth="1.7" /></svg>;
return <svg viewBox="0 0 24 24" aria-hidden="true"><rect fill="none" height="6" rx="1" stroke="currentColor" strokeWidth="1.7" width="6" x="3" y="3" /><rect fill="none" height="6" rx="1" stroke="currentColor" strokeWidth="1.7" width="6" x="15" y="3" /><rect fill="none" height="6" rx="1" stroke="currentColor" strokeWidth="1.7" width="6" x="3" y="15" /><rect fill="none" height="6" rx="1" stroke="currentColor" strokeWidth="1.7" width="6" x="15" y="15" /></svg>;
}
export function GlassDock({ items = defaultItems, selectedId, defaultSelectedId = items[0]?.id, onSelect, headline = "Find your flow.", description = "A tactile navigation surface with a little atmosphere built in.", paused = false, children, className = "" }: GlassDockProps) {
const [internalSelected, setInternalSelected] = useState(defaultSelectedId);
const activeId = selectedId ?? internalSelected;
const activeItem = items.find((item) => item.id === activeId) ?? items[0];
const select = (item: GlassDockItem) => { if (selectedId === undefined) setInternalSelected(item.id); onSelect?.(item); };
return (
<section className={`stepkit stepkit-showcase stepkit-glass-dock${className ? ` ${className}` : ""}`} data-paused={paused} aria-label="Glass dock navigation showcase">
<div className="stepkit-glass-dock__landscape" aria-hidden="true"><span className="stepkit-glass-dock__sun" /></div>
<div className="stepkit-glass-dock__copy"><span className="stepkit-glass-dock__eyebrow">Navigation / glass dock</span><h2 className="stepkit-glass-dock__title">{headline}</h2><p className="stepkit-glass-dock__description">{description}</p>{children}</div>
<span className="stepkit-glass-dock__selection" aria-live="polite">Selected: {activeItem?.label ?? "None"}</span>
<nav className="stepkit-glass-dock__dock" aria-label="Dock destinations">{items.slice(0, 5).map((item) => <button className="stepkit-glass-dock__item" data-paused={paused} aria-label={item.label} aria-pressed={activeId === item.id} type="button" key={item.id} onClick={() => select(item)}><DockIcon icon={item.icon} />{item.badge ? <span aria-label="New" /> : null}</button>)}</nav>
</section>
);
}
export default GlassDock;
components/GlassDock.tsxcomponents/showcase.csscomponents/styles.cssInstall
Use the registry command or download the complete source archive.
npx [email protected] add https://vstepanov.com/stepkit/r/glass-dock.jsonIntegration prompt
Install Stepkit Glass dock 0.3.0 from the registry and preserve the included MIT notice. npx [email protected] add https://vstepanov.com/stepkit/v0.3.0/r/glass-dock.json Install the tested runtime dependencies exactly: npm install [email protected] [email protected] Keep the component source, shared styles, NOTICE.md, and provenance files together. Apply the theme tokens on a wrapper rather than passing theme keys as component props. Usage: ```tsx "use client"; import type { CSSProperties } from "react"; import { GlassDock } from "./components/stepkit/GlassDock"; import "./components/stepkit/styles.css"; import "./components/stepkit/showcase.css"; const themeStyle = {"--stepkit-bg":"#f7f7f8","--stepkit-surface":"#ffffff","--stepkit-surface-muted":"#f0f0f3","--stepkit-fg":"#17181c","--stepkit-muted":"#626572","--stepkit-border":"#e3e4e9","--stepkit-border-strong":"#c8cad3","--stepkit-code":"#f1f1f4","--stepkit-shadow":"0 20px 60px rgba(31, 26, 64, .10)","--stepkit-accent":"#6950df","--stepkit-accent-strong":"#5137c7","--stepkit-accent-soft":"#eeeafd","--stepkit-accent-on-soft":"#5137c7","--stepkit-accent-text":"#ffffff","--stepkit-error":"#b43145","--stepkit-placeholder":"#626572","--stepkit-radius":"16px","--stepkit-space":"16px","--stepkit-control-height":"44px","--stepkit-font-sans":"Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif"} as CSSProperties; export default function Example() { return ( <div className="stepkit-theme" data-stepkit-preset="modern" style={themeStyle}> <GlassDock headline={"Find your flow."} paused={false} /> </div> ); } ``` Selected bounded settings: {"headline":"Find your flow.","paused":false}. Selected theme: {"mode":"light","accent":"violet","radius":"soft","density":"comfortable","preset":"modern"}.
Props
| Name | Type | Default | Description |
|---|---|---|---|
items | GlassDockItem[] | default items | Up to five destinations. |
selectedId | string | undefined | Controlled selected destination. |
onSelect | (item: GlassDockItem) => void | undefined | Called after a destination is selected. |
paused | boolean | false | Disables pointer magnification. |
Accessibility & limits
- Each dock destination is a native button with an accessible label and pressed state.
- Selected destination is announced in a live region.
- Magnification is supplemental and does not hide labels.
Limitations
- The landscape is an original CSS composition; provide a separate image slot if a product needs photography.
Provenance
Original source under the MIT licence.
Original Stepkit implementation.
Required notices: NOTICE.md, PROVENANCE-stepkit-glass-dock.md