{"id":18550129,"url":"https://github.com/lambdaisland/ornament","last_synced_at":"2025-04-06T01:08:03.680Z","repository":{"id":43711671,"uuid":"395547927","full_name":"lambdaisland/ornament","owner":"lambdaisland","description":"Clojure Styled Components","archived":false,"fork":false,"pushed_at":"2024-08-06T09:31:48.000Z","size":219,"stargazers_count":121,"open_issues_count":4,"forks_count":13,"subscribers_count":9,"default_branch":"main","last_synced_at":"2024-10-30T05:42:19.728Z","etag":null,"topics":["clojure","clojurescript","css","reagent"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lambdaisland.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-08-13T06:49:17.000Z","updated_at":"2024-10-24T12:04:49.000Z","dependencies_parsed_at":"2024-11-06T21:03:31.805Z","dependency_job_id":"1c431c8c-7eca-4dea-8c3c-bccb55d07fd5","html_url":"https://github.com/lambdaisland/ornament","commit_stats":{"total_commits":99,"total_committers":7,"mean_commits":"14.142857142857142","dds":"0.12121212121212122","last_synced_commit":"12f843e8b00081d5e743fc734e96750491788a89"},"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lambdaisland%2Fornament","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lambdaisland%2Fornament/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lambdaisland%2Fornament/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lambdaisland%2Fornament/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lambdaisland","download_url":"https://codeload.github.com/lambdaisland/ornament/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247419860,"owners_count":20936012,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clojure","clojurescript","css","reagent"],"created_at":"2024-11-06T21:03:26.604Z","updated_at":"2025-04-06T01:08:03.663Z","avatar_url":"https://github.com/lambdaisland.png","language":"Clojure","funding_links":["https://opencollective.com/lambda-island","http://opencollective.com/lambda-island"],"categories":[],"sub_categories":[],"readme":"# Ornament\n\n\u003c!-- badges --\u003e\n[![cljdoc badge](https://cljdoc.org/badge/com.lambdaisland/ornament)](https://cljdoc.org/d/com.lambdaisland/ornament) [![Clojars Project](https://img.shields.io/clojars/v/com.lambdaisland/ornament.svg)](https://clojars.org/com.lambdaisland/ornament)\n\u003c!-- /badges --\u003e\n\nCSS-in-Clj(s)\n\n## Features\n\n- Define styled components\n- Use Garden syntax for CSS, or Girouette (Tailwind-style) utility class names\n- Have styling live close to, but separate from DOM structure\n- Compile to plain CSS files as a compile step\n- Use components in any Hiccup implementation, frontend or backend\n\n\u003c!-- installation --\u003e\n## Installation\n\nTo use the latest release, add the following to your `deps.edn` ([Clojure CLI](https://clojure.org/guides/deps_and_cli))\n\n```\ncom.lambdaisland/ornament {:mvn/version \"1.12.107\"}\n```\n\nor add the following to your `project.clj` ([Leiningen](https://leiningen.org/))\n\n```\n[com.lambdaisland/ornament \"1.12.107\"]\n```\n\u003c!-- /installation --\u003e\n\n## Introduction\n\nOrnament is the culmination of many discussions and explorations with the aim to\nfind the sweet spot in how to handle styling in large Clojure or ClojureScript\nweb projects. It takes ideas from CSS-in-JS approaches, and utility-class\nlibraries, while (in our opinion) improving on both.\n\nAt the heart of ornament is the `defstyled` macro, which defines a \"styled\ncomponent\". It combines a name, a HTML tag, and styling rules.\n\n```clojure\n(require '[lambdaisland.ornament :as o])\n\n(o/defstyled freebies-link :a\n  {:font-size \"1rem\"\n   :color \"#cff9cf\"\n   :text-decoration \"underline\"})\n```\n\nThis does two things, first of all it creates a Hiccup component, which combines\nthe tag (`:a` in this case), with a class name based on the component name. (See\nthe section on \"Choosing a Hiccup Implementation\" for more info on where this\nsyntax is supported.)\n\n```clojure\n;; Hiccup\n[freebies-link {:href \"/episodes/interceptors-concepts\"} \"Freebies\"]\n```\n\nWhich renders as:\n\n```html\n\u003ca class=\"lambdaisland_episodes__freebies_link\"\u003eFreebies\u003c/a\u003e\n```\n\nThe styling information is rendered during a build step to CSS, and written out,\nso it gets served as any other plain CSS file.\n\n```clojure\n(spit \"resource/public/ornament.css\" (o/defined-styles))\n```\n\n```css\n.lambdaisland_episodes__freebies_link {\n  font-size: 1rem;\n  color: #cff9cf;\n  text-decoration: underline;\n}\n```\n\nIf you prefer to use [Girouette](https://github.com/green-coder/girouette)\n(Tailwind) utility classes (a.k.a. tokens), then you can do that as well, or you\ncan mix and match. Note that for compatibility reasons Ornament will continue to\ndefault to Tailwind v2 components, but you can opt-in to v3, see the section\nbelow.\n\n```clojure\n(o/defstyled freebies-link :a\n  :text-base\n  :text-green-500\n  :underline)\n```\n\nFinally you can add one or more function bodies to your component, which acts as\na render function, determining how the children of the component will render.\nNote that this render function only determines the \"inside\" of the component, it\nwill still get wrapped with the tag and class name passed to `defstyled`.\n\n```clojure\n(o/defstyled page-grid :div\n  :relative :h-full :md:flex\n  [:\u003e.content :flex-1 :p-2 :md:p-10 :bg-gray-100]\n  ([sidebar content]\n   [:\u003c\u003e\n    sidebar\n    [:div.content\n     content]]))\n```\n\nHopefully these examples have sufficiently whetted your appetite. We'll explain\nthe syntax and features of `defstyled` in detail down below. But first we need\nto explain Ornament's philosophy on how to deal with CSS, to give you accurate\nexpectations of how it will behave. This is especially relevant for\nClojureScript projects.\n\n## Choosing your Tailwind version\n\nWe rely on [Girouette](https://github.com/green-coder/girouette) to provide us\nwith a re-implementation in Clojure of Tailwinds components, classes, and\nstyles. At time of writing Girouette is compatible with either Tailwind 2.0.3,\nor 3.0.24.\n\nTo use Tailwind 3 tokens (classes), pass an extra `:tw-version 3` option to\n`set-tokens!`\n\n```clj\n(o/set-tokens! {:tw-version 3})\n```\n\nSee \"Customizing Girouette\" below for more info on `set-tokens`, or check the\ndocstring.\n\nThe `girouette.tw.preflight` namespace provides the Tailwind preflight (reset)\nstylesheet for either v2 or v3. If you are using `o/defined-styles` you can also\nopt to have it automatically prepended. This function also accepts a similar\n`:tw-version` argument (`2` or `3)\n\n```clj\n(o/defined-styles {:preflight? true :tw-version 3})\n```\n\n## Choosing a Hiccup Implementation\n\nRepresenting HTML using nested Clojure vectors is an approach that was\npopularized by the [Hiccup](https://github.com/weavejester/hiccup) library, and\nso this is referred to as Hiccup-syntax. Since then many other libaries have\nimplemented this same syntax, sometimes with small changes or extensions.\n\nA particular extension popularized by\n[Reagent](https://github.com/reagent-project/reagent) is the use of functions as\ncomponents. You define functions which return hiccup, and use those functions in\nyour hiccup where normally you would have a keyword denoting a HTML element tag.\nIn essence you're no longer calling the function yourself (with parentheses) but\ntelling Reagent to call it while rendering.\n\n```clj\n(defn my-component [arg]\n  [:p \"in my component:\" arg])\n  \n(reagent-dom/render [my-component \"hello\"] (js/document.getElementById \"app\"))\n```\n\nThis has significance in React-apps because it allows React/Reagent to manage\nthe component, but we like it in general to make it clear that something is\nconceptually a hiccup component.\n\nOrnament components are basically the same, they are (or behave like) functions\nwhich return Hiccup. If the implementation you are using supports putting them\nin square brackets then you can go with that, or you can just call them yourself\nas a function.\n\n```clj\n(o/defstyled my-component :p\n  :m-3)\n\n;; option 1\n[my-component \"hello\"]\n\n;; option 2\n(my-component \"hello\")\n```\n\nHow do different Hiccup implementations differ?\n\n- Clojure vs ClojureScript (or both with cljc)\n- Support functions as components\n- Automatically HTML-escape text (the ones that support this all have different ways of injecting \"raw\" html strings)\n- Support for `:\u003c\u003e` (fragments)\n- Have the style property be a map (instead of just a string)\n- Pre-compile via macros for better performance\n\nSome that we know of:\n\nClojure:\n\n- Hiccup 1, beware: does not auto-escape text and so is sensitive to CSRF attacks\n- Hiccup 2, fixes the security issue and is generally the better option, despite officially being in alpha\n- Enlive (See `net.cgrand.enlive-html/html`)\n- [lambdaisland.hiccup](https://github.com/lambdaisland/hiccup) is our take on\n  this, it is based on Enlive, but adds functions-as-component, fragments,\n  styles-as-maps, and a convenient syntax for raw html strings\n- Hicada (cljc, macro-based)\n\nClojureScript\n\n- Reagent and other React wrappers\n- Sablono\n- Hicada\n\n## Ornament CSS Compilation\n\nOrnament is written in CLJC, meaning you can call `defstyled` in ClojureScript\nexactly the same way as in Clojure. But, there's one important distinction, we\ndo not add any styling information (be it Garden, Girouette, or CSS), to your\nClojureScript build.\n\nThis is an important design decision, and it's worth elaborating a bit. We\nunderstand the appeal of something like CSS-in-JS from a _syntax and programmer\nconvenience_ point of view, and we try to offer the same kind of convenience.\nHowever, we question that it is sensible to deal with CSS generation in\nJavaScript. We think it's vastly superior to generate CSS once at build/deploy\ntime, and to then deal with it *as CSS*.\n\nThis pays dividends, we don't need to add all the machinery of Garden and\nGirouette to the frontend build, neither do we need all the styling definitions,\nso Ornament will have minimal impact on your bundle size. And your CSS can be\nserved — and cached — as a simple static CSS file.\n\nWhat you do get from using `defstyled` in ClojureScript is a component\n(function) which you can use in Hiccup (e.g. Reagent) to render HTML. The\ncomponent knows about the HTML tag to use, the CSS class name to add, and if you\nadded a render function, it knows about that as well.\n\nSo where does the styling go? We add that to a registry *during macroexpansion*.\nIn other words: in Clojure. Once all `defstyled` invocations have been compiled,\nyou can grab the full CSS with `(o/defined-styles)`, and spit it to a file.\n\nBefore you grab the output of `(o/defined-styles)` you need to make sure all\nyour styles have in fact been defined. For Clojure projects this merely means\nrequiring all namespaces. If you have some kind of main application entry point\nthat loads all your components/views, then load that, and capture the styles\nonce it has finished.\n\n### ClojureScript\n\nFor ClojureScript you have two options, either you define all you components in\n`cljc` files, and use the same approach as in Clojure. The alternative is to\nfirst run your ClojureScript build, and then in the same process write out the\nstyles. You can for instance write your own script that invokes the\nClojureScript build API, then follows it up by writing out the styles, or you\ncan use something like Shadow-cljs build hooks.\n\n#### Limitations\n\nKeep in mind that the style (Garden/CSS) section of a component is only ever\nprocessed in Clojure, even when used in ClojureScript files. This means that it\nis not possible to reference ClojureScript variables or functions.\n\n```clojure\n(def sizes {:s \"0.5rem\" :m \"1.rem\" :l \"2rem\"})\n\n(o/defstyled foo :div\n  {:padding (:m sizes)})\n```\n\nThis will work in Clojure, but not ClojureScript. Referencing\nvariables/functions inside the component body is not a problem.\n\n```clojure\n(def divider [:hr])\n\n(o/defstyled dividers :div\n  {:padding \"1rem\"}\n  ([\u0026 children]\n   (into [:\u003c\u003e]\n         (interpose divider)\n         children)))\n```\n\nThis works in both Clojure and ClojureScript.\n\nNote that it *is* possible to reference previously defined `defstyled`\ncomponents in the style rules section, even in ClojureScript, see the section\n\"Referencing other components in Rules\" below.\n\n```clojure\n(o/defstyled referenced :div\n  {:color :blue})\n\n(o/defstyled referer :p\n  [referenced {:color :red}] ;; use as classname\n  [:.foo referenced]) ;; use as style rule\n```\n\n#### Computing values (referencing vars) inside style rules in cljc files\n\nStyle rules are processed during macroexpansion, which happens in Clojure, even\nwhen compiling ClojureScript. This means that any code inside style rules needs\nto be able to evaluate in Clojure by the time ClojureScript starts compiling the\n`defstyled` form.\n\nConsider this namespace\n\n```clj\n;; components.cljc\n(ns my.components\n  (:require [lambdaisland.ornament :as o]))\n\n(def my-tokens {:main-color \"green\"})\n\n(o/defstyled with-code :div\n  {:background-color (-\u003e my-tokens :main-color)})\n```\n\nIn Clojure this works as you would expect but when compiling this as a\nClojureScript file it fails.  The failure is due to the file never being loaded\nas a Clojure namespace, so the Clojure var `#'my.components/my-tokens` doesn't\nexist in the Clojure environment which is where macro expansion takes place.\n\nTo fix this you can use the `:require-macros` directive which instructs the\nClojureScript compiler to load a given Clojure namespace (in this case the\ncurrent namespace) before continuing the compilation of the current namespace.\n\n```clj\n(ns my.components\n  (:require [lambdaisland.ornament :as o])\n  #?(:cljs (:require-macros my.components)))\n\n#?(:clj\n   (def my-tokens {:main-color \"green\"}))\n\n(o/defstyled with-code :div\n  {:background-color (-\u003e my-tokens :main-color)})\n```\n\nThis addresses the problem by instructing the ClojureScript compiler to first\nload `my.components` as a clj namespace thereby creating the `#'my-tokens`\nClojure var. The compiler then continues with the cljs compilation and when it\ngets to expansion of the `defstyled` macro form (specifically processing of the\nstyle rule `{:background-color ...}`) the `#'my-tokens` var is available in the\nclj environment and evaluated for it's value which is then included in the\nOrnament style registry.\n\nWrapping `my-tokens` in `#?(:clj ...)` is not strictly necessary, but it helps\nto emphasize the point that this definition is only ever used on the Clojure\nside, you don't need it in your compiled ClojureScript.\n\n##### Important note about CLJC files and clojure.core symbols\n\nIf you do __any__ computation in your style rules it is recommended that you\nrequire the file as clj by utilising the `:require-macros` directive to self\nrequire the namespace.\n\n```clj\n(ns my.components\n  (:require [lambdaisland.ornament :as o])\n  #?(:cljs (:require-macros my.components)))\n\n(o/defstyled with-code :div\n  {:background-color (str \"red\")})\n```\n\nThe need for this is a subtle consequence of the same interaction between\nClojure and the ClojureScript compiler outlined in the previous section. A\nsimilar issue will manifest if you try to use `clojure.core` symbols in your\nstyle rules without requiring the clj namespace. This is surprising at first but\nmakes sense after considering that it is the `ns` form that auto refers all\n`clojure.core` symbols into the current namespace. It follows that if the `ns`\nform is not evaluated, because we do not require the cljc file as a clj file,\nthen no symbols mapping to the `clojure.core` symbols will have created in the\ncurrent namespace. The `clojure.core` symbols are however interned, and as such\nfully qualifying the namespace will work but that is not an ergonomic solution. \n\n```clj\n(ns my.components\n  (:require [lambdaisland.ornament :as o]))\n\n;; Does not work\n(o/defstyled with-code :div\n  {:background-color (str \"red\")})\n\n;; Does work but not recommended\n(o/defstyled with-code :div\n  {:background-color (clojure.core/str \"red\")})\n```\n\n#### Shadow-cljs build hook example\n\nThis is enough to get recompilation of your styles to CSS, which shadow-cljs\nwill then hot-reload.\n\n```clojure\n;; Easiest to just make this a clj file.\n(ns my.hooks\n  (:require [lambdaisland.ornament :as o]\n            [garden.compiler :as gc]\n            [girouette.tw.preflight :as girouette-preflight]))\n\n;; Optional, but it's common to still have some style rules that are not\n;; component-specific, so you can use Garden directly for that\n(def global-styles\n  [[:html {:font-size \"14pt\"}]])\n\n(defn write-styles-hook\n  {:shadow.build/stage :flush}\n  [build-state \u0026 args]\n  ;; In case your global-styles is in a separate clj file you will have to\n  ;; reload it yourself, shadow only reloads/recompiles cljs/cljc files\n  #_(require my.styles :reload)\n  ;; Just writing out the CSS is enough, shadow will pick it up (make sure you\n  ;; have a \u003clink href=styles.css rel=stylesheet\u003e)\n  (spit \"resources/public/styles.css\"\n        (str\n         ;; `defined-styles` takes a :preflight? flag, but we like to have some\n         ;; style rules between the preflight and the components. This whole bit\n         ;; is optional.\n         (gc/compile-css (concat\n                          girouette-preflight/preflight-v2_0_3\n                          styles/global-styles))\n         \"\\n\"\n         (o/defined-styles)))\n  build-state)\n```\n\n```clojure\n;; shadow-cljs.edn\n{,,,\n\n ;; For best results, otherwise you will find that some styles are missing after\n ;; restarting the shadow process\n :cache-blockers #{lambdaisland.ornament}\n\n :builds\n {:main\n  {:target     :browser\n   ,,,\n   :build-hooks [(my.hooks/write-styles-hook)]}}}\n```\n\n## Defstyled Component Syntax\n\n`defstyled` really does two things, the macro expands to a form like\n\n```clojure\n(def footer (reify StyledComponent ...))\n```\n\nThis \"styled component\" acts as a function, which is what makes it compatible\nwith Hiccup implementations.\n\n```clojure\n(footer \"hello\")\n;;=\u003e\n[:footer {:class [\"project-discovery_parts__footer\"]} \"hello\"]\n```\n\nIt also implements various protocol methods. You don't typically need to call\nthese yourself, but they can be useful for verifying how your component behaves.\n\n```clojure\n(o/classname footer)  \n;;=\u003e \"project-discovery_parts__footer\"\n(o/tag footer)\n;;=\u003e :footer\n(o/rules footer)\n;;=\u003e [{:max-width \"60rem\"}]\n(o/as-garden footer)\n;;=\u003e [\".project-discovery_parts__footer\" {:max-width \"60rem\"}]\n(o/css footer)\n;;=\u003e \".project-discovery_parts__footer{max-width:60rem}\"\n```\n\n### Component Name\n\nThe first argument to `defstyled` is the component name, this will create a var\nwith the given name, containing the component. A function-like object that can\nbe used to render HTML.\n\nThe name will also determine the class name that will be used in the HTML and\nCSS. For this Ornament combines the namespace name with the component name, and\nmunges them to be valid CSS identifiers.\n\n```clojure\n(ns my.views\n (:require [lambdaisland.ornament :as o]))\n\n(o/defstyled footer :footer\n  {:max-width \"60rem\"})\n  \n(o/classname footer)\n;; my_views__footer\n```\n\nYou can use metadata on the namespace to change the namespace prefix.\n\n``` clojure\n(ns ^{:ornament/prefix \"views\"} com.company.project.frontend.views\n (:require [lambdaisland.ornament :as o]))\n\n(o/defstyled footer :footer\n  {:max-width \"60rem\"})\n  \n(o/classname footer)\n;; views__footer\n```\n\nUsing fully qualified var names as class names provides the unexpected benefit\nthat it becomes trivial to find the component you are looking at in your\nbrowser's inspector.\n\nWhen stringifying the component you also get the class name back, this allows\nusing them to reference a certain class, for instance in Hiccup:\n\n```clojure\n[:div {:class (str footer)} ...]\n\n;; Depending on your Hiccup implementation this can also work\n[:div {:class [footer]} ...]\n\n(js/querySelector (str footer))\n```\n\n### HTML Tag\n\nThe second argument to `defstyled` is the HTML tag. This is typically a keyword,\nlike `:section`, `:tr`, or `:div`. This is used when rendering the component as\nHiccup, and so anything that is valid in your Hiccup implementation of choice is\nfair game, including `:div#some-id` or `:p.a_class`. You could also use for\ninstance a Reagent component, assuming it correctly handles receiving a\nproperties map as its first argument. You can't use `:\u003c\u003e` as the tag, since we\ncan't add a class name to a fragment.\n\nAs a special case you can use another styled component as the tag.\n\n```clojure\n(defstyled about-footer footer\n  ,,,)\n```\n\nThis will cause the new component to \"inherit\" both the HTML tag used by the\nreferenced componet, and any CSS rules it defines.\n\n### Rules\n\nAfter the name and tag, `defstyled` takes one or more \"rules\". These can be\nmaps, vectors, or keywords.\n\nMaps and vectors are handled by [Garden](https://github.com/noprompt/garden),\nand we recommend reading the Garden documentation and getting familiar with the\nsyntax. Maps define CSS styles as demonstrated before, with vectors you can\napply styles to descendant elements, or handle pseudo-elements.\n\n```clojure\n(in-ns 'my-nav)\n\n(o/defstyled menu :nav\n  {:padding \"2rem\"}\n  [:a {:color \"blue\"}]\n  [:\u0026:hover {:background-color \"#888\"}])\n \n;; Inspect the result\n(o/css menu)\n```\n\nResult:\n\n```css\n.my_nav__menu{padding:2rem}\n.my_nav__menu a{color:blue}\n.my_nav__menu:hover{background-color:#888}\n```\n\nKeywords are handled by [Girouette](https://github.com/green-coder/girouette).\nGirouette uses a grammar to parse utility class names like `:text-green-500`,\nand converting the result to Garden syntax. Out of the box it supports all the\nsame names that Tailwind provides, but you can define custom rules, or adjust\nthe color palette. (See [Customizing Girouette](#customizing-girouette)).\n\nNote that you can mix and match these. You should be able to use a Girouette\nkeyword anywhere where you would use a Garden properties map.\n\n#### Referencing other components in Rules\n\nYou can use a previously defined `defstyled` component either as a selector, or\nas a style rule.\n\nConsider this \"call to action\" button.\n\n```clojure\n(o/defstyled cta :button\n  {:background-color \"red\"})\n```\n\nYou might use it as part of another component, and add additional styling for\nthat context.\n\n```clojure\n(o/defstyled buy-now-section :div\n  [cta {:padding \"2rem\"}]\n  ([]\n   [:\u003c\u003e\n    [:p \"The best widgest in the world\"]\n    [cta {:value \"Buy now!\"}]]))\n```\n\nHere `cta` is a shorthand for writing the full Ornament class name of the\ncomponent. Now the `cta` button will get some extra padding in this context, in\naddition to its red background.\n\nYou can also use `cta` as a reusable group of styles. In this case we want to\nstyle the `:a` element with the `cta` styles.\n\n```clojure\n(o/defstyled pricing-link :span\n  [:a cta]\n  ([]\n   [:a {:href \"/pricing\"} \"Pricing\"]))\n```\n\nThis kind of referencing previously defined components works both in Clojure and\nClojureScript, even though in ClojureScript usage you can't normally reference\nvars inside your style declaration. To make these work we resolve these symbols\nduring compilation based on Ornament's registry of components.\n\n## Render functions\n\nAfter the component name, tag, and CSS rules, you can optionally put one or more\nrender functions, consisting of an argument vector, and the function body.\n\n```clojure\n(o/defstyled with-body :p\n  :px-5 :py-3 :rounded-xl\n  {:color \"azure\"}\n  ([\u0026 children]\n   (into [:strong] children)))\n   \n[with-body \"hello\"]\n;;=\u003e\n\"\u003cp class=\\\"ot__with_body\\\"\u003e\u003cstrong\u003ehello\u003c/strong\u003e\u003c/p\u003e\"\n```\n\nYou can put multiple of these to deal with multiple arities\n\n```clojure\n(o/defstyled multi-arity :p\n  ([arg1]\n   [:strong arg1])\n  ([arg1 arg2]\n   [:\u003c\u003e\n    [:strong arg1] [:em arg2]]))\n```\n\nWithout render functions a styled component works almost like a plain HTML tag\nwhen using in Hiccup: the first argument, if it's a map, is treated as a map of\nHTML attributes, any following arguments are treated as children.\n\nWhen you supply your own render function this behavior changes. All arguments\nare passed to the render function, which then determines the element's\nattributes and children.\n\nTo set custom attributes on the outer element from inside the render function,\nyou use a properties map together with a fragment `:\u003c\u003e` identifier:\n\n```clojure\n(o/defstyled my-compo :div\n ([props]\n  [:\u003c\u003e {:title \"hello\"} \"hello!\"]))\n```\n\nIf you pass a `:class` here it will get added to the class that Ornament\ngenerates for the component.\n\nWhen using a component that has a custom render function, you can set attributes\nby using the special `:lambdaisland.ornament/attrs` keyword.\n\n```clojure\n[my-compo {:regular-prop 123 ::o/attrs {:title \"heyo\"}}]\n```\n\nAny `:class` or `:style` attributes passed in this way will be added to any\nclasses or styles set inside the render function with `:\u003c\u003e`. Optionally for\n`:class` and `:style` you can replace the values instead of appending by adding\na `^:replace` metadata on the vector / map.\n\n```clojure\n[my-compo {::o/attrs {:class ^:replace [\"one-class\" \"other-class\"]\n                      :style {:text-color \"blue\"}}}]\n```\n\nIn previous versions we supported `:class`, `:id` and `:style` at the top of the\nproperties map, but that's no longer the case.\n\nThere's an additional mechanic for setting attributes from inside the\nrender-function, through metadata on the return value, but it is considered\ndeprecated, since it's superseded by `[:\u003c\u003e {,,,attrs,,,}]`.\n\n```clojure\n(o/defstyled nav-link :a\n  ([{:keys [id]}]\n   (let [{:keys [url title description]} (get-route id)]\n     ^{:href url :title description}\n     [:\u003c\u003e title])))\n\n;;=\u003e\n\u003ca href=\"/videos\" title=\"Watch amazing videos\" class=\"ot__nav_link\"\u003eVideos\u003c/a\u003e\n```\n\n## Differences from Garden\n\nThe rules section of a component is essentially\n[Garden](https://github.com/noprompt/garden) syntax. We run it through the\nGarden compiler, and so things that work in Garden generally work there as well,\nwith some exceptions.\n\nKeywords that come first inside a vector are always treated as CSS selectors, as\nyou would expect, but if they occur elsewhere then we first pass them to\nGirouette to expand to style rules class names. If Girouette does not recognize\nthe keyword as a classname, then it's preserved in the Garden as-is.\n\nThat means that generally things work as expected, since selectors and Girouette\nclasses don't have much overlap.\n\n```clojure\n;; ✔️ :ol is recognized as a selector\n\n(o/defstyled list-wrapper :div\n  [:ul :ol {:background-color \"blue\"}])\n\n(o/css list-wrapper)\n;; =\u003e \".ot__list_wrapper ul,.ot__list_wrapper ol{background-color:blue}\"\n\n;; ✔️ :bg-blue-500 is recognized as a utility class\n\n(o/defstyled list-wrapper :div\n  [:ul :bg-blue-500])\n\n(o/css list-wrapper)\n;; =\u003e \".ot__list_wrapper ul{--gi-bg-opacity:1;background-color:rgba(59,130,246,var(--gi-bg-opacity))}\"\n```\n\nBut there is some potential for clashes, e.g. Girouette has a `:table` class.\n\n```clojure\n;; ❌ not what we wanted\n\n(o/defstyled fig-wrapper :div\n  [:figure :table {:padding \"1rem\"}])\n  \n(o/css fig-wrapper)\n;; =\u003e \".ot__fig_wrapper figure{display:table;padding:1rem}\"\n```\n\nInstead use a set to make it explicit that these are multiple selectors. It's\ngood practice to do this in general since it is more explicit and reduces\nambiguity and chance of clashes.\n\n```clojure\n(o/defstyled fig-wrapper :div\n  [#{:figure :table} {:padding \"1rem\"}])\n\n(o/css fig-wrapper)\n;; =\u003e \".ot__fig_wrapper figure,.ot__fig_wrapper table{padding:1rem}\"\n```\n\n### Garden Extensions\n\nOrnament does a certain amount of pre-processing before passing the rules over\nto Garden for compilation. This allows us to support some extra syntax which we\nfind more convenient.\n\n### Special \"tags\"\n\nUse these as the first element in a vector to opt into special handling. Some of\nthese are used where a selector would be used, others are helpers for defining\nproperty values.\n\n- `:at-media`\n\nYou can add breakpoints for responsiveness to your components with `:at-media`.\n\n```clojure\n(o/defstyled eps-container :div\n  {:display \"grid\"\n   :grid-gap \"1rem\"\n   :grid-template-columns \"repeat(auto-fill, minmax(20rem, 1fr))\"\n   :padding \"0 1rem 1rem\"}\n  [:at-media {:min-width \"40rem\"}\n   {:grid-gap \"2rem\"\n    :padding \"0 2rem 2rem\"}])\n```\n\n- `:cssfn`\n\nCSS functions can be invoked with `:cssfn`\n\n```clojure\n(o/defstyled with-css-fn :a\n  [:\u0026:after {:content [:cssfn :attr \"href\"]}])\n```\n\n- `:at-supports`\n\nSupport for feature tests via `@supports`\n\n```clojure\n(o/defstyled feature-check :div\n  [:at-supports {:display \"grid\"}\n   {:display \"grid\"}])\n```\n\n- `:rgb` / `:hsl` / `:rgba` / `:hsla`\n\nShorthands for color functions\n\n```clojure\n(o/defstyled color-fns :div\n  {:color [:rgb 150 30 75]\n   :background-color [:hsla 235 100 50 0.5]})\n\n(o/css color-fns)\n;;=\u003e\n\".ot__color_fns{color:#961e4b;background-color:hsla(235,100%,50%,0.5)}\"\n```\n\n- `:str`\n\nTurns any strings into quoted strings, for cases where you need to put string content in your CSS.\n\n```clojure\n(o/defstyled with-css-fn :a\n  [:\u0026:after {:content [:str \" (\" [:cssfn :attr \"href\"] \")\"]}])\n```\n\n#### Special property handling\n\nSome property names we recognize and treat special, mainly to make it less\ntedious to define composite values.\n\n- `:grid-area` / `:border` / `:margin` / `:padding`\n\nTreat vector values as space-separated lists, e.g. `:padding [10 0 15 0]`.\nNon-vector values are passed on unchanged.\n\n- `:grid-template-areas`\n\nUse nested vectors to define the areas\n\n```clojure\n   :grid-template-areas [[\"title\"      \"title\"      \"user\"]\n                         [\"controlbar\" \"controlbar\" \"controlbar\"]\n                         [\"....\"       \"....\"       \"....\"]\n                         [\"....\"       \"....\"       \"....\"]\n                         [\"....\"       \"....\"       \"....\"]\n```\n\n## Customizing Girouette\n\nGirouette is highly customizable. Out of the box it supports the same classes as\nTailwind does, but you can customize the colors, fonts, or add completely new\nrules for recognizing class name.\n\nThe `girouette-api` atom contains the result of `giroutte/make-api`. By\nreplacing it you can customize how keywords are expanded to Garden. We provide a\n`set-tokens!` function which makes the common cases straightforward. This\nconfigures Girouette, so that these tokens become available inside Ornament\nstyle declarations.\n\n`set-tokens!` takes a map with these (optional) keys:\n\n- `:colors` : map from keyword to 6-digit hex color, without leading `#`\n- `:fonts`: map from keyword to font stack (comman separated string)\n- `:components`: sequence of Girouette components, each a map with `:id`\n  (keyword), `:rules` (string, instaparse, can be omitted), and `:garden` (map,\n  or function taking instaparse results and returning Garden map)\n- `:tw-version`: which Girouette defaults to use, either based on Tailwind\n  v2, or v3. Valid values: `2`, `3`. Defaults to v2.\n\n```clojure\n(o/set-tokens! {:colors {:primary \"001122\"}\n                :fonts {:system \"-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji\"}\n                :components [{:id :full-center\n                              :garden {:display \"inline-flex\"\n                                       :align-items \"center\"}}\n                             {:id :full-center-bis\n                              :garden [:\u0026 :inline-flex :items-center]}\n                             {:id :custom-bullets\n                              :rules \"custom-bullets = \u003c'bullets-'\u003e bullet-char\n                                 \u003cbullet-char\u003e = #\\\".\\\"\"\n                              :garden (fn [{[bullet-char] :component-data}]\n                                        [:\u0026\n                                         {:list-style \"none\"\n                                          :padding 0\n                                          :margin 0}\n                                         [:li\n                                          {:padding-left \"1rem\"\n                                           :text-indent \"-0.7rem\"}]\n                                         [\"li:before\"\n                                          {:content bullet-char}]])}]})\n```\n\nLet's go over these. Colors is straightforward, it introduces a new color name,\nso now I can use classes like `:text-primary-500` or `:bg-primary`.\n\nFonts provide the `:font-\u003cname\u003e` class, so in this case `:font-system`.\n\nWith custom components there's a lot you can do. The first one here,\n`:full-center`, only has a `:garden` key, which has plain data as its value.\nThis basically provides an alias or shorthand, so we can use `:full-center` in\nplace of `{:display \"inline-flex\" :align-items \"center\"}`. The second one,\n`:full-center-bis` is essentially the same, but we've used other Girouette\nclasses. Just as in `defstyled` you can use those too.\n\nThe third one introduces a completely custom rule. It has a `:rules` key, which\ngets a string using Instaparse grammar syntax. Here we're definining a grammar\nwhich will recognize any classname starting with \"bullets-\" and followed by a\nsingle character.\n\nIf `:rules` is omitted we assume this is a static token, and we'll generate a\nrule of the form `token-id = \u003c'token-id'\u003e`. That's what happens with the first\ntwo components.\n\nIn this case the `:garden` key gets a function, which receives the parse\ninformation (under the `:component-data` key), and can use it to build up the\nGarden styling. Notice that we're using the \"bullet-char\" that we parsed out of\nthe class name, to set the `:content` on `:li:before`.\n\nThe end result is that we can do something like this:\n\n```clojure\n(o/defstyled bear-list :ul\n  :bullets-🐻)\n  \n[bear-list\n [:li \"Black\"]\n [:li \"Formosan\"]]\n```\n\nAnd get a bullet list which uses bear emojis for the bullets.\n\n`set-tokens!` will add the new colors, fonts, and components to the defaults that\nGirouette provides. You can change that by adding a `^:replace` tag (this uses\nmeta-merge). e.g. `{:colors ^:replace {...}}`) \n\n\u003c!-- opencollective --\u003e\n## Lambda Island Open Source\n\nThank you! ornament is made possible thanks to our generous backers. [Become a\nbacker on OpenCollective](https://opencollective.com/lambda-island) so that we\ncan continue to make ornament better.\n\n\u003ca href=\"https://opencollective.com/lambda-island\"\u003e\n\u003cimg src=\"https://opencollective.com/lambda-island/organizations.svg?avatarHeight=46\u0026width=800\u0026button=false\"\u003e\n\u003cimg src=\"https://opencollective.com/lambda-island/individuals.svg?avatarHeight=46\u0026width=800\u0026button=false\"\u003e\n\u003c/a\u003e\n\u003cimg align=\"left\" src=\"https://github.com/lambdaisland/open-source/raw/master/artwork/lighthouse_readme.png\"\u003e\n\n\u0026nbsp;\n\nornament is part of a growing collection of quality Clojure libraries created and maintained\nby the fine folks at [Gaiwan](https://gaiwan.co).\n\nPay it forward by [becoming a backer on our Open Collective](http://opencollective.com/lambda-island),\nso that we may continue to enjoy a thriving Clojure ecosystem.\n\nYou can find an overview of our projects at [lambdaisland/open-source](https://github.com/lambdaisland/open-source).\n\n\u0026nbsp;\n\n\u0026nbsp;\n\u003c!-- /opencollective --\u003e\n\n\u003c!-- contributing --\u003e\n## Contributing\n\nEveryone has a right to submit patches to ornament, and thus become a contributor.\n\nContributors MUST\n\n- adhere to the [LambdaIsland Clojure Style Guide](https://nextjournal.com/lambdaisland/clojure-style-guide)\n- write patches that solve a problem. Start by stating the problem, then supply a minimal solution. `*`\n- agree to license their contributions as EPL 1.0.\n- not break the contract with downstream consumers. `**`\n- not break the tests.\n\nContributors SHOULD\n\n- update the CHANGELOG and README.\n- add tests for new functionality.\n\nIf you submit a pull request that adheres to these rules, then it will almost\ncertainly be merged immediately. However some things may require more\nconsideration. If you add new dependencies, or significantly increase the API\nsurface, then we need to decide if these changes are in line with the project's\ngoals. In this case you can start by [writing a pitch](https://nextjournal.com/lambdaisland/pitch-template),\nand collecting feedback on it.\n\n`*` This goes for features too, a feature needs to solve a problem. State the problem it solves, then supply a minimal solution.\n\n`**` As long as this project has not seen a public release (i.e. is not on Clojars)\nwe may still consider making breaking changes, if there is consensus that the\nchanges are justified.\n\u003c!-- /contributing --\u003e\n\n\u003c!-- license --\u003e\n## License\n\nCopyright \u0026copy; 2021-2022 Arne Brasseur and contributors\n\nAvailable under the terms of the Eclipse Public License 1.0, see LICENSE.txt\n\u003c!-- /license --\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flambdaisland%2Fornament","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flambdaisland%2Fornament","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flambdaisland%2Fornament/lists"}