Portfolio
Portfolio is a ClojureScript "visual REPL" — it renders UI components in named, isolated scenes so you can see every component state at a glance. It is similar to Storybook.js but designed for REPL-driven ClojureScript development.
Current release: no.cjohansen/portfolio {:mvn/version "2026.03.1"}
Quick Decision: What do you need?
| Task | Go to |
|---|---|
| Install, start the UI, shadow-cljs setup | setup.md |
Define scenes with defscene, params, atoms, lifecycle | defscene.md |
| Choose a framework adapter (Reagent, React, Replicant…) | defscene.md |
| Organize scenes into collections, folders, packages | organization.md |
| Customize background, viewport, grid, CSS, HTML | customize-ui.md |
Use tap> from the REPL without defining a scene | setup.md |
| Disable Portfolio in production builds | setup.md |
| Common mistakes and things to avoid | anti-patterns.md |
Core Mental Model
Portfolio renders scenes — named snapshots of a component in a specific state. Scenes live in regular ClojureScript namespaces (typically named *-scenes), and Portfolio automatically organizes them into a navigable sidebar by namespace hierarchy.
defscene → scene → namespace (package) → folder → sidebar
The UI runs in the browser at your dev server address. Re-evaluating a defscene at the REPL re-renders it immediately — no page refresh needed.
Minimal Working Example
;; deps.edn
{:deps {no.cjohansen/portfolio {:mvn/version "2026.03.1"}}}
;; src/my/button_scenes.cljs
(ns my.button-scenes
(:require [portfolio.reagent :refer-macros [defscene]]))
(defscene default-button
[:button.btn "Click me"])
(defscene disabled-button
[:button.btn {:disabled true} "Can't click"])
;; src/my/dev/portfolio.cljs — call this from your shadow-cljs :runner-ns
(ns my.dev.portfolio
(:require [portfolio.ui :as ui]))
(ui/start! {:config {:css-paths ["/css/app.css"]}})
shadow-cljs :portfolio Build Target
Auto-loads every namespace whose name ends in -scenes. The fastest setup path:
;; shadow-cljs.edn
{:deps {:aliases [:dev]}
:dev-http {8080 ["dev-resources/public" "classpath:public"]}
:builds
{:portfolio
{:target :portfolio
:runner-ns my.dev.portfolio ;; omit to start with no config
:dev {:output-dir "dev-resources/public/js"}}}}
"classpath:public" in :dev-http is required — it serves Portfolio's bundled assets from the jar.
Default Advice
- Keep scenes in a dedicated directory (
dev/,scenes/, or a:devalias source path) so they are never included in production builds. - Use
:paramswhenever the component data is non-trivial — it enables REPL tinkering and search indexing. - Prefer the
:portfolioshadow-cljs target over manual namespace loading for new projects. - Do not suppress Portfolio in production via compiler flags if you can simply exclude scene namespaces from the production build instead.