apps/design-system/content/docs/icons.mdx
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.
We rely on Lucide for any standard UI icon needs.
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 />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 icons use strokeWidth={1.5} and fill-only icons use stroke="none". These defaults come from the source SVG's root attributes and can be intentionally overridden at the call site.
Follow these steps to add a new custom icon to the Supabase icon library.
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:
viewBox="0 0 24 24"stroke="currentColor" for strokes (no hardcoded colors)stroke-width="1.5"fill="none" for fills (no hardcoded colors)<clipPath>, <defs>, and <g> wrappers have been removed<path> elementsFor 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.
Put shared styling like fill, stroke, stroke-width, stroke-linecap, and stroke-linejoin on the root <svg>. The build validates this contract, propagates the root attributes as the component's defaults, and converts them to camel-case for React compatibility (e.g. strokeWidth). Child-level stroke styling is rejected because it would override props passed to the component.
Build the component: Run npm run build:icons from inside the packages/icons directory
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.
Icons should:
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>
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>
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>
The icon build reports the source file and invalid attribute when an SVG does not follow the stroke or fill-only contract. Run pnpm validate:icons from packages/icons to check sources without regenerating components.