LearnAPI Reference

Replicant

Build simpler, more testable UIs with pure functions and data. Separate rendering from domain logic and state, and finally enjoy true functional programming when building user interfaces.

Learn ReplicantAPI Reference

Create user interfaces with data

Build user interfaces with hiccup — plain old Clojure data literals like vectors, keywords, maps and strings. Render to strings on the server or render (and re-render) the live DOM in browsers, just like you would with React and its peers.

[:div.media-thumb
 [:a {:href "https://vimeo.com/861600197"}
  [:img.rounded-lg
   {:src "/images/data-driven.png"
    :alt "Watch Stateless, Data-driven UIs"}]
  [:div.overlay
   [:a.btn.btn-circle
    {:data-theme "cupcake"
     :href "https://vimeo.com/861600197"}
    [:svg.h-4.w-4
     {:xmlns "http://www.w3.org/2000/svg"
      :viewBox "0 0 256 256"
      :style {:display "inline-block"
              :line-height "1"}}
     [:path {:d "M72,39.88V216.12a8,8,0,0..."
             :fill "none"
             :stroke "currentColor"}]]]]]]
Watch talk: Stateless, Data-driven UIs

Hiccup is highly expressive and unlike JSX does not require any additional build step — it's just Clojure. Replicant's dialect supports some features not found in other libraries, learn more in the hiccup reference.

Reusable UI elements with pure functions

Structure your UI in reusable bits and pieces with regular Clojure functions. Keep these pure functions in cljc files and use them on the server or on the client. No framework specific component abstractions required.

(defn Media [{:keys [theme thumbnail url
                     title text button]}]
  [:div.media {:data-theme theme}
   [:aside.media-thumb
    (Thumbnail thumbnail)]
   [:main.grow
    [:a.hover:underline {:href url}
     [:h2.font-bold title]
     [:p text]]]
   (Button (assoc button :style :ghost))])

While domain-aware UI components like Video and LikeButton can look neat in quick demos and conference talks, coupling the business domain with rendering logic scales poorly and leads to duplicated UI code. Replicant encourages generic data-driven UI elements by not providing a stateful component abstraction. Learn why generic elements improve frontend code-bases.

Data-driven interactivity

Even event handlers can be data, giving you the option of handling them all in a single global handler function. Your UI remains pure data, event handlers declare their intended effects, and you can trivially test the UI even when it supports user interactivity.

(replicant.dom/set-dispatch!
 (fn handle-dom-event [rd [action]]
   (case action
     ::search-videos
     (let [q (-> rd :replicant/dom-event
                 .-target .-value)]
       (swap! store search-videos q)))))

(defn render [state]
  [:div
   [:h2 "Parens of the dead episodes"]
   [:label.input-field
    [:input.grow
     {:type "text"
      :placeholder "Search"
      :on {:input [::search-videos]}}]
    (icons/render icon)]
   (MediaList
    {:medias (->> (:results state)
                  (map video->media-data))})])

Replicant imposes no structure on your event data — it's just passed to the global event handler. You're free to make your own declarative interactivity DSL. Oh, and event handlers can be regular functions as well.
Learn more about event handlers.

Easy data-driven transitions

Replicant allows you to specify overrides for any attributes during mounting and unmounting. This allows you to declaratively transition elements on mount and unmount, like fading in an element as it's mounted:

(when (:visible? props)
  [:div {:style {:transition "opacity 0.25s"
                 :opacity 1}
         :replicant/mounting {:style {:opacity 0}}
         :replicant/unmounting {:style {:opacity 0}}}
   (Media
    {:thumbnail {:image "/images/christian.jpg"
                 :size 12}
     :title "Christian Johansen"
     :text (list "Just wrote some documentation for Replicant."
                 [:span.opacity-50 "Posted December 11th 2024"])})])

Christian Johansen

Just wrote some documentation for Replicant

Posted December 11th 2024

Hide post

This is not limited to inline styles, you can stick any attribute in :replicant/mounting and :replicant/unmounting, like :class. Learn more tidbits like this in the hiccup reference.

Extend Replicant with custom element aliases

Custom element aliases can extend Replicant's hiccup vocabulary. Aliases are stateless wrappers that can expand to arbitrary hiccup. Alias functions are only called when their arguments change, and they can receive side-chained data, removing the need to pass certain data everywhere. Perfect for data that is static (e.g. i18n dictionaries), or change very infrequently (e.g. locales).

[:div.media
 [:aside.media-thumb
  [:img.rounded-lg {:src (:person/profile-pic author)}]]
 [:main.grow
  [:h2.font-bold (:person/full-name author)]
  [:p (:post/text post)]
  [:p.opacity-50
   [:i18n/k ::posted {:date (:post/created-at post)}]]]]

Christian Johansen

Just wrote some documentation for Replicant

Posted December 11th 2024

In this example, :i18n/k is a user-provided extension that uses a library to seemingly give Replicant built-in i18n capabilities. And it's all still data. Learn more about aliases, and check out the i18n tutorial.

You can test your UI

When the entire UI is represented by data created by a pure function, testing is trivial: Pass in some data, assert that it appears in the UI somehow. Rinse and repeat. You can even verify that event handlers will "do" what you expect, as long as they're expressed as data.

(defn MediaList [{:keys [title medias]}]
  [:div.media-list
   (when title (typo/h2 title))
   (map Media medias)])

(defn prepare-ui-data [{:keys [user video]}]
  {:title (str (count videos) " videos")
   :medias (map #(video->media-data user %) videos)})

;; Take some domain data
(->> @store
     ;; Convert it to UI data
     prepare-ui-data
     ;; Convert it to hiccup
     MediaList
     ;; ...and render it
     (r/render el))
(deftest prepare-ui-data-test
  (testing "Uses episode number for title"
    (is (= (->> {:videos [{:episode/number 1}]}
                prepare-ui-data :medias first
                :title)
           "Episode 1")))

  (testing "Includes a like button"
    (is (= (->> {:videos [{:video/id "v898900"
                           :episode/number 1}]
                 :user {:user/id "u09b"}}
                prepare-ui-data :medias first
                :button)
           {:title "Like video"
            :icon :phosphor.regular/heart
            :on {:click [[:command/like-video
                          {:user/id "u09b"
                           :video/id "v898900"}]]}}))))

Pure functions like video->media-data capture the essentials of turning your domain data into a visual user interface without the volatile details of markup — excellent targets for plain old unit tests. No elaborate browser automation required. Check out the getting started tutorial for a practical demonstration.

What are developers saying?

Functional programming in the UI? It always felt like a pipe dream, and React was the closest we would ever get. Then I stumbled across Replicant and the game changed completely. I hadn’t realized how much I had longed for building fully data oriented UIs!

Peter Strömberg, frontend developer, creator of Calva

Unidirectional data flow

With Replicant you always render the entire UI, starting at the root node. There are no atoms, subscriptions, sub-tree rendering, component-local state, or other moving parts. Just a pure function that takes in your domain data and returns the entire UI. Boring in a good way. Simple AND easy.

Learn more about how Replicant makes this possible, and why you shouldn't worry about performance in
Why top-down rendering is the best frontend programming model.

Replicant logo byjaide/eccentric-j.
This website source on Github.