Icons

Icons make actions and navigation across Supabase easier.

Principles

  1. Paired: Icons should accompany text, as they aren’t often obvious enough on their own.
  2. Clear: Icons should be legible at small sizes and unembellished. Let the text do the heavy lifting.
  3. Consistent: Use the same icons for similar actions throughout Supabase. This makes the app easier to use.

Tints

Use classes just like you would for text to tint icons. For example:

<BucketAdd className="text-foreground-muted" />

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.

UI icons

We rely on Lucide 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.

Usage

import { BucketAdd, InsertCode, ReplaceCode } 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 default to size={24}. Stroke and fill defaults come from the source SVG's root attributes (e.g. stroke-width="1" on the root <svg> becomes the component's default strokeWidth). You can override those defaults at the call site, but any stroke, fill, or stroke-width set on individual child paths will still win.

Adding new custom icons

Follow these steps 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 follows these requirements:

    • Exported at 24×24px with viewBox="0 0 24 24"
    • Uses stroke="currentColor" for strokes (no hardcoded colors)
    • Uses stroke-width="1.5" (deviate based on optical weight if necessary)
    • Uses fill="none" for fills (no hardcoded colors)
    • Icon content is optically centered and around 18×18px within the 24×24 frame
    • Any unnecessary elements like <clipPath>, <defs>, and <g> wrappers have been removed
    • SVG structure is as simple as possible with just <path> elements

    For fill-only icons (e.g. logos that use shapes instead of strokes), add stroke="none" to the root <svg> element. The build will propagate this so the component never renders an unwanted stroke.

Prefer putting shared styling like fill, stroke, stroke-width, stroke-linecap, and stroke-linejoin on the root <svg>. The build propagates those root attributes as the component's defaults, and also converts them to camel-case for React compatibility (e.g. strokeWidth). If you put those attributes on individual child paths instead, they become fixed path-level styling and will override props passed to the component.

  1. Build the component: Run npm run build:icons from inside the packages/icons directory

  2. Use the icon: Import and use like any other icon:

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

The icon name is automatically made available in camel-case, determined by the kebab-case input SVG. For example, my-new-icon.svg will become available as MyNewIcon.

SVG design guidelines

Icons should:

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

Bad example ❌

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

<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 (stroke icon) ✅

Clean structure with currentColor and proper attributes:

<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>

Good example (fill-only icon / logo) ✅

Note stroke="none" on the root to prevent unwanted strokes, and fill="currentColor" on each path:

<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="none" xmlns="http://www.w3.org/2000/svg">
  <path d="M..." fill="currentColor" />
</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 on individual child paths, they will override the component's strokeWidth prop. Keep shared stroke attributes on the root <svg> and remove them from individual paths if you want consumers to control stroke weight.