Icons

ShipClojure ships two icon systems:

  1. Phosphor icons (recommended for new code) — ~9,000 icons inlined at compile time via a macro; no build step, no manual SVG management.
  2. SVG sprite — vendored SVGs compiled into a single sprite via bb run build-icons; still used by existing components.

Phosphor icons

Phosphor icons ship as pre-parsed EDN resources inside the no.cjohansen/phosphor-clj jar. The saas.common.ui.phosphor/icon macro reads the referenced icon at compile time and inlines it as a static UIx <svg> element — only icons you actually reference end up in the JS bundle, and the same code renders on the JVM for SSR.

(ns example
  (:require
   [saas.common.ui.phosphor :as picon]
   [uix.core :refer [$ defui]]))

(defui notification-bell []
  ($ :button {:class "btn btn-ghost btn-circle"}
     (picon/icon :phosphor.regular/bell {:class "size-5"})))

Icon ids follow :phosphor.<style>/<name> where style is one of bold, duotone, fill, light, regular, thin. Browse the catalog at phosphoricons.com; the kebab-case icon name maps directly (e.g. "rocket-launch" → :phosphor.regular/rocket-launch).

Icons default to 1em square and use currentColor, so they scale with the surrounding font size and inherit text color. Style them with Tailwind classes:

(picon/icon :phosphor.fill/heart {:class "size-8 text-error"})

The optional attrs map also supports :size/:color/:style (set as inline styles) and any other SVG attribute; unknown icon ids fail at compile time with a clear error.

Because inlining happens at macro expansion, the icon id must be a literal keyword. For dynamic icons, use a literal call per branch:

(if ok?
  (picon/icon :phosphor.regular/check-circle {:class "text-success"})
  (picon/icon :phosphor.regular/x-circle {:class "text-error"}))

See the "Phosphor Icon Scenes" collection in the portfolio (bb dev, then http://localhost:10005) for live examples.

SVG sprite (legacy)

ShipClojure uses SVG sprites for optimal icon performance. You'll find raw SVGs in the resources/icons directory. These are then compiled into a sprite using the bb run build-icons script, which generates the sprite.svg file, which is loaded into your html.

Here is a blog post I wrote about the full technique used in ShipClojure.

The SVGs used by default in ShipClojure come from lucide.dev. You can download additional SVG icons from there, or provide your own. Once you've added new files in the icons directory, run bb run build-icons and you can then use the icon.cljc component to render it.

(ns example
 (:require
    [uix.core :refer [$ defui]]
    [saas.common.ui.icon :refer [icon]]))

(defui my-cool-component
    []
    ($ :div
      ($ icon {:icon :moon
               :size :lg
               :class "text-red-300"})))

The :icon or :name prop is the name of the file without the .svg extension. We recommend using kebab-case filenames rather than PascalCase to avoid casing issues with different operating systems.

Note that the build_icons.clj script automatically removes width and height props from your SVGs to ensure they scale properly.

By default, all the icons will have a height and width of 1em so they should match the font-size of the text they're next to. You can also customize the size using the :size prop or :class "w-8 h-8"

You can use the sprite.svg on server rendered pages also, and even in the blog templates like so:

<div>
    <svg class="inline self-center w-[1em] h-[1em]">
       <use href="/svg/sprite.svg#moon" />
    </svg>
</div>