Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 29 additions & 11 deletions apps/design-system/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,48 @@

Design resources for building consistent user experiences at Supabase.

## Getting Started
## Getting started

First, make a copy of _.env.local.example_ and name it _env.local_. Then run the development server as described below.
First, make a copy of _.env.local.example_ and name it _env.local_. Then install any required packages and start the development server:

From within this _design-system_ directory, run:
```bash
cd apps/design-system
pnpm i
pnpm dev:full
```

The `dev:full` command runs both the Next.js development server and Contentlayer concurrently, which is recommended for most development workflows.

### Alternative commands

You can also run the development server and content watcher separately:

```bash
# Run only the Next.js development server
pnpm dev

# Run only the content watcher (in a separate terminal shell)
pnpm content:dev
```

Or from the root directory run:
Or run the development server from the root directory:

```bash
pnpm dev:design-system
```

Open [http://localhost:3003](http://localhost:3003) with your browser to see the result.

### Watching for MDX changes
To run both the development server and content watcher from the root directory, you can use:

If you would like to watch for changes to MDX files with hot reload, you can run the following command in a separate terminal shell:
```bash
# Run the development server
pnpm dev:design-system

# Run the content watcher (in a separate terminal shell)
pnpm --filter=design-system content:dev
```
pnpm content:dev
```

This runs Contentlayer concurrently and watches for any changes.
Open [http://localhost:3003](http://localhost:3003) in your browser to see the result.

### Watching for MDX changes

The `dev:full` command automatically watches for changes to MDX files with hot reload. If you're running the `pnpm dev` separately, you'll need to run `pnpm content:dev` in a separate terminal shell to watch for content changes.
33 changes: 33 additions & 0 deletions apps/design-system/__registry__/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -918,6 +918,39 @@ export const Index: Record<string, any> = {
subcategory: "undefined",
chunks: []
},
"nav-menu-demo": {
name: "nav-menu-demo",
type: "components:example",
registryDependencies: ["nav-menu"],
component: React.lazy(() => import("@/registry/default/example/nav-menu-demo")),
source: "",
files: ["registry/default/example/nav-menu-demo.tsx"],
category: "undefined",
subcategory: "undefined",
chunks: []
},
"nav-menu-badges": {
name: "nav-menu-badges",
type: "components:example",
registryDependencies: ["nav-menu"],
component: React.lazy(() => import("@/registry/default/example/nav-menu-badges")),
source: "",
files: ["registry/default/example/nav-menu-badges.tsx"],
category: "undefined",
subcategory: "undefined",
chunks: []
},
"nav-menu-icons": {
name: "nav-menu-icons",
type: "components:example",
registryDependencies: ["nav-menu"],
component: React.lazy(() => import("@/registry/default/example/nav-menu-icons")),
source: "",
files: ["registry/default/example/nav-menu-icons.tsx"],
category: "undefined",
subcategory: "undefined",
chunks: []
},
"popover-demo": {
name: "popover-demo",
type: "components:example",
Expand Down
15 changes: 15 additions & 0 deletions apps/design-system/config/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,16 @@ export const docsConfig: DocsConfig = {
},
],
},
{
title: 'UI Patterns',
items: [
{
title: 'Navigation',
href: '/docs/ui-patterns/navigation',
items: [],
},
],
},
{
title: 'Fragment Components',
items: [
Expand Down Expand Up @@ -289,6 +299,11 @@ export const docsConfig: DocsConfig = {
href: '/docs/components/menubar',
items: [],
},
{
title: 'NavMenu',
href: '/docs/components/nav-menu',
items: [],
},
{
title: 'Navigation Menu',
href: '/docs/components/navigation-menu',
Expand Down
8 changes: 8 additions & 0 deletions apps/design-system/content/docs/changelog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ description: Latest updates and announcements.
toc: false
---

## 2nd Oct 2025 - Update button styles

- UI Patterns section added for higher-level guidance
- [`Navigation`](/design-system/docs/ui-patterns/navigation) UI pattern added
- [`NavMenu`](/design-system/docs/components/nav-menu) component added

[PR](https://github.com/supabase/supabase/pull/39188)

## 1st Sep 2025 - Update button styles

- [`Color usage`](/design-system/docs/color-usage) page updated
Expand Down
57 changes: 57 additions & 0 deletions apps/design-system/content/docs/components/nav-menu.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
title: NavMenu
description: A horizontal list of related views within a consistent context.
component: true
---

<Admonition type="warning" title="Consider renaming">
This component is titled very similarly to the [Navigation Menu](../components/navigation-menu)
component. Consider renaming it to something like TabMenu or
[UnderlineNav](https://primer.style/product/components/underline-nav/).
</Admonition>

<ComponentPreview
name="nav-menu-demo"
className="[&_.preview>[data-orientation=vertical]]:sm:max-w-[70%]"
peekCode
wide
/>

## Usage

NavBar is exclusively used as sub-navigation within PageLayout.

```tsx
import { NavMenu, NavMenuItem } from 'ui'
```

```tsx
<NavMenu>
<NavMenuItem active={true}>
<Link href="#">Overview</Link>
</NavMenuItem>
<NavMenuItem active={false}>
<Link href="#">Documentation</Link>
</NavMenuItem>
</NavMenu>
```

## Examples

### With counter badges

You may include counter badges to indicate the number of items within a tab, for example the amount of buckets in storage.

<ComponentPreview
name="nav-menu-badges"
className="[&_.preview>[data-orientation=vertical]]:sm:max-w-[70%]"
/>

### With icons

You may include icons to more clearly represent the content of each tab. Every tab should have an icon (or no icon).

<ComponentPreview
name="nav-menu-icons"
className="[&_.preview>[data-orientation=vertical]]:sm:max-w-[70%]"
/>
104 changes: 99 additions & 5 deletions apps/design-system/content/docs/icons.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,108 @@ description: Icons make actions and navigation across Supabase easier.

## Tints

Destructive actions, such as deleting an API key, don’t need to be [tinted](color-usage#text) with `text-destructive` because there should be a confirmation dialog as a failsafe right after.
Use classes just like you would for [text](color-usage#text) to tint icons. For example:

## UI Icons
```jsx
<BucketAdd className="text-foreground-muted" />
```

We rely on Lucide icons for most of our UI icons.
Just like text, don’t tint icons with `text-destructive` for destructive actions. There should be a confirmation dialog right after which can handle the destructive styling.

## Custom Icons
## UI icons

Tap on an icon below to copy the JSX, SVG, or import path.
We rely on [Lucide](https://lucide.dev/icons/) for any standard UI icon needs.

## Custom icons

Create and use custom icons when Lucide doesn’t have the icon you need. Tap on an icon below to copy the JSX, SVG, or import path.

<Icons />

### Usage

```jsx
import { ReplaceCode, InsertCode, BucketAdd } from 'icons'

function app() {
return (
<>
<ReplaceCode className="text-light" strokeWidth={1} size={16} />
<InsertCode className="text-light" strokeWidth={1} size={16} />
<BucketAdd size={24} className="text-foreground-muted" />
</>
)
}
```

**Default props**: All icons have `strokeWidth={2}` and `size={24}` by default. Override these props as needed for your use case.

### Adding new custom icons

To add a new custom icon to the Supabase icon library:

1. **Create SVG file**: Add your SVG file to `packages/icons/src/raw-icons/` with a kebab-case name (e.g., `my-new-icon.svg`). Make sure it has follows these exact requirements:

- Exported at 24x24px with `viewBox="0 0 24 24"`
- Uses `stroke="currentColor"` for strokes (no hardcoded colors)
- Uses `fill="none"` for fills (no hardcoded colors)
- Icon content is optically centered and around 18x18px within the 24x24 frame
- Any unnecessary elements like `<clipPath>`, `<defs>`, and `<g>` wrappers have been removed
- SVG structure is as simple as possible with just `<path>` elements

Just leave attributes like `stroke-width` as they are. The conversion to camel-case (for React compatibility) is handled by the below build process.

2. **Build the component**: Run `npm run build:icons` from inside the `packages/icons` directory

3. **Use the icon**: Import and use like any other icon:

```jsx
import { MyNewIcon } from 'icons'
;<MyNewIcon size={16} strokeWidth={1} />
```

### SVG design guidelines

Icons should:

- Always be exported 24x24px
- Have an icon inside that frame that’s around 18x18px(ish)
- Use clean, simple paths without unnecessary wrapper elements

#### Bad example ❌

Notice the hardcoded colors, unnecessary backgrounds, and complex structure:

```svg
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="24" height="24" fill="#1E1E1E" /> <!-- ❌ Hardcoded color -->
<path d="M..." fill="#404040" /> <!-- ❌ Hardcoded color -->
<path d="M..." stroke="#EDEDED" stroke-linecap="round" /> <!-- ❌ Hardcoded color -->
</svg>
```

#### Good example ✅

Clean structure with `currentColor` and proper attributes:

```svg
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M6 7C6 4.2 8.2 2 11 2H13C15.8 2 18 4.2 18 7" />
<path d="M4.5 11H19.5" />
<path d="M6 11L6.8 20C6.9 21.1 7.9 22 9 22H12" />
</svg>
```

{/* This is still wrong: */}

```svg
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M6 7C6 4.2 8.2 2 11 2H13C15.8 2 18 4.2 18 7" />
<path d="M4.5 11H19.5" />
<path d="M6 11L6.8 20C6.9 21.1 7.9 22 9 22H12" />
</svg>
```

### Troubleshooting

If your SVG specifies `stroke-width` attributes, they will override the component's `strokeWidth` prop. Remove stroke attributes from individual paths to let the component control them.
14 changes: 14 additions & 0 deletions apps/design-system/content/docs/ui-patterns/navigation.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
title: Navigation
description: Navigation patterns help users understand where they are and where they can go next.
---

Supabase has a necessarily complex navigation system to handle multiple products and levels of hierarchy. This page introduces those general patterns, best practices, and the components involved.

## Components

### NavMenu

A horizontal list of related views within a consistent PageLayout context, allowing for clearer page-level organisation. Activating a NavMenu item should trigger a URL change.

[NavMenu component guidelines](../components/nav-menu)
2 changes: 2 additions & 0 deletions apps/design-system/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"scripts": {
"preinstall": "npx only-allow pnpm",
"dev": "next dev --turbopack --port 3003",
"dev:full": "concurrently \"pnpm dev\" \"pnpm content:dev\"",
"build": "pnpm run content:build && pnpm run build:registry && next build --turbopack",
"build:registry": "tsx --tsconfig ./tsconfig.scripts.json ./scripts/build-registry.mts && prettier --log-level silent --write \"registry/**/*.{ts,tsx,mdx}\" --cache",
"start": "next start",
Expand Down Expand Up @@ -53,6 +54,7 @@
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"config": "workspace:^",
"concurrently": "^8.2.2",
"mdast-util-toc": "^6.1.1",
"postcss": "^8.5.3",
"rimraf": "^4.1.3",
Expand Down
25 changes: 25 additions & 0 deletions apps/design-system/registry/default/example/nav-menu-badges.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Link from 'next/link'
import { Badge, NavMenu, NavMenuItem } from 'ui'

export default function NavMenuWithIcons() {
return (
<NavMenu>
<NavMenuItem active={true}>
<Link href="#" className="inline-flex items-center gap-2">
Buckets
<Badge variant="default">10</Badge>
</Link>
</NavMenuItem>
<NavMenuItem active={false}>
<Link href="#" className="inline-flex items-center gap-2">
Policies <Badge variant="default">2</Badge>
</Link>
</NavMenuItem>
<NavMenuItem active={false}>
<Link href="#" className="inline-flex items-center gap-2">
Settings
</Link>
</NavMenuItem>
</NavMenu>
)
}
21 changes: 21 additions & 0 deletions apps/design-system/registry/default/example/nav-menu-demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Link from 'next/link'
import { NavMenu, NavMenuItem } from 'ui'

export default function NavMenuDemo() {
return (
<NavMenu>
<NavMenuItem active={true}>
<Link href="#">Overview</Link>
</NavMenuItem>
<NavMenuItem active={false}>
<Link href="#">Invocations</Link>
</NavMenuItem>
<NavMenuItem active={false}>
<Link href="#">Logs</Link>
</NavMenuItem>
<NavMenuItem active={false}>
<Link href="#">Code</Link>
</NavMenuItem>
</NavMenu>
)
}
Loading
Loading