{"id":13646127,"url":"https://github.com/clj-commons/cljss","last_synced_at":"2025-12-12T01:18:27.041Z","repository":{"id":17008142,"uuid":"80995148","full_name":"clj-commons/cljss","owner":"clj-commons","description":"Clojure Style Sheets — CSS-in-JS for ClojureScript","archived":false,"fork":false,"pushed_at":"2023-04-18T04:39:23.000Z","size":279,"stargazers_count":455,"open_issues_count":23,"forks_count":26,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-10-14T22:55:54.220Z","etag":null,"topics":["clojurescript","css-in-cljs","css-in-js","dynamic-styles"],"latest_commit_sha":null,"homepage":"https://roman01la.github.io/cljss/","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/clj-commons.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2017-02-05T13:37:36.000Z","updated_at":"2024-12-08T17:11:29.000Z","dependencies_parsed_at":"2024-01-14T09:59:29.495Z","dependency_job_id":"5fa5e881-8f92-4f09-99f4-03a859e535b5","html_url":"https://github.com/clj-commons/cljss","commit_stats":{"total_commits":195,"total_committers":9,"mean_commits":"21.666666666666668","dds":0.08205128205128209,"last_synced_commit":"daf4bfca2b915f3bd0a8f8211dfdab62d683accf"},"previous_names":["roman01la/cljss"],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/clj-commons/cljss","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clj-commons%2Fcljss","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clj-commons%2Fcljss/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clj-commons%2Fcljss/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clj-commons%2Fcljss/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/clj-commons","download_url":"https://codeload.github.com/clj-commons/cljss/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clj-commons%2Fcljss/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":27673723,"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","status":"online","status_checked_at":"2025-12-11T02:00:11.302Z","response_time":56,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["clojurescript","css-in-cljs","css-in-js","dynamic-styles"],"created_at":"2024-08-02T01:02:49.085Z","updated_at":"2025-12-12T01:18:27.005Z","avatar_url":"https://github.com/clj-commons.png","language":"Clojure","funding_links":["https://www.patreon.com/bePatron?c=1239559"],"categories":["Clojure"],"sub_categories":[],"readme":"# Clojure Style Sheets\n\n\u003cimg src=\"logo.png\" width=\"155\" height=\"68\" alt=\"cljss logo\" /\u003e\n\n[CSS-in-JS](https://speakerdeck.com/vjeux/react-css-in-js) for ClojureScript\n\n[![Clojars](https://img.shields.io/clojars/v/clj-commons/cljss.svg)](https://clojars.org/clj-commons/cljss)\n[![cljdoc badge](https://cljdoc.org/badge/clj-commons/cljss)](https://cljdoc.org/d/clj-commons/cljss/CURRENT)\n[![CircleCI](https://circleci.com/gh/clj-commons/cljss.svg?style=svg)](https://circleci.com/gh/clj-commons/cljss)\n\nAsk questions on #cljss chat at [Clojuarians Slack](http://clojurians.net/)\n\n\u003ca href=\"https://www.patreon.com/bePatron?c=1239559\"\u003e\n  \u003cimg src=\"https://c5.patreon.com/external/logo/become_a_patron_button.png\" height=\"40px\" /\u003e\n\u003c/a\u003e\n\n`[clj-commons/cljss \"1.6.4\"]`\n\n## Table of Contents\n\n- [Why write CSS in ClojureScript?](#why-write-css-in-clojurescript)\n- [Features](#features)\n- [How it works](#how-it-works)\n- [Usage](#usage)\n- [Composing styles](#composing-styles)\n- [Development workflow](#development-workflow)\n- [Production build](#production-build)\n- [Roadmap](#roadmap)\n- [Contributing](#contributing)\n- [Supporters](#supporters)\n- [License](#license)\n\n## Why write CSS in ClojureScript?\n\nWriting styles this way has the same benefits as writing components that keep together view logic and presentation. It all comes to developer efficiency and maintainability.\n\nThease are some resources that can give you more context:\n\n- [“A Unified Styling Language”](https://medium.com/seek-blog/a-unified-styling-language-d0c208de2660) by Mark Dalgleish\n- [“A Unified Styling Language”](https://www.youtube.com/watch?v=X_uTCnaRe94) (talk) by Mark Dalgleish\n- [“The road to styled components: CSS in component-based systems”](https://www.youtube.com/watch?v=MT4D_DioYC8) by Glen Maddern\n\n## Features\n\n- Automatic scoped styles by generating unique names\n- CSS pseudo-classes and pseudo-elements\n- CSS animations via `@keyframes` at-rule\n- CSS Media Queries\n- Nested CSS selectors\n- Injects styles into `\u003cstyle\u003e` tag at run-time\n- Debuggable styles in development (set via `goog.DEBUG`)\n- Fast, 1000 insertions in under 100ms\n\n## How it works\n\n### `defstyles`\n\n`defstyles` macro expands into a function which accepts an arbitrary number of arguments and returns a string of auto-generated class names that references both static and dynamic styles.\n\n```clojure\n(defstyles button [bg]\n  {:font-size \"14px\"\n   :background-color bg})\n\n(button \"#000\")\n;; \"-css-43696 -vars-43696\"\n```\n\nDynamic styles are updated via CSS Variables (see [browser support](http://caniuse.com/#feat=css-variables)).\n\n### `defstyled`\n\n`defstyled` macro accepts var name, HTML element tag name as a keyword and a hash of styles.\n\nThe macro expands into a function which accepts an optional hash of attributes and child components, and returns a React element. It is available for the Om, Rum and Reagent libraries. Each of them are in corresponding namespaces: `cljss.om/defstyled`, `cljss.rum/defstyled` and `cljss.reagent/defstyled`.\n\nA hash of attributes with dynamic CSS values as well as normal HTML attributes can be passed into the underlying React element. Reading from the attributes hash map can be done via anything that satisfies `cljs.core/ifn?` predicate (`Fn` and `IFn` protocols, and normal functions).\n\n_NOTE: Dynamic props that are used only to compute styles are also passed onto React element and thus result in adding an unknown attribute on a DOM node. To prevent this it is recommended to use keyword or a function marked with `with-meta` so the library can remove those props (see example below)._\n\n- keyword value — reads the value from props map and removes matching attribute\n- `with-meta` with a single keyword — passes a value of a specified attribute from props map into a function and removes matching attribute\n- `with-meta` with a collection of keywords — passes values of specified attributes from props map into a function in the order of these attributes and removes matching attributes\n\n```clojure\n(defstyled h1 :h1\n  {:font-family \"sans-serif\"\n   :font-size :size ;; reads the value and removes custom `:size` attribute\n   :color (with-meta #(get {:light \"#fff\" :dark \"#000\"} %) :color)} ;; gets `:color` value and removes this attribute\n   :padding (with-meta #(str %1 \" \" %2) [:padding-v :padding-h])) ;; gets values of specified attrs as arguments and remove those attrs\n\n(h1 {:size \"32px\" ;; custom attr\n     :color :dark ;; custom attr\n     :padding-v \"8px\" ;; custom attr\n     :padding-h \"4px\" ;; custom attr\n     :margin \"8px 16px\"} ;; normal CSS rule\n    \"Hello, world!\")\n;; (js/React.createElement \"h1\" #js {:className \"css-43697 vars-43697\"} \"Hello, world!\")\n```\n\n#### predicate attributes in `defstyled`\n\nSometimes you want toggle between two values. In this example a menu item can switch between active and non-active styles using `:active?` attribute.\n\n```clojure\n(defstyled MenuItem :li\n  {:color (with-meta #(if % \"black\" \"grey\") :active?)})\n\n(MenuItem {:active? true})\n```\n\nBecause this pattern is so common there's a special treatment for predicate attributes (keywords ending with `?`) in styles definition.\n\n```clojure\n(defstyled MenuItem :li\n  {:color \"grey\"\n   :active? {:color \"black\"}})\n\n(MenuItem {:active? true})\n```\n\n### pseudo-classes\n\nCSS pseudo classes can be expressed as a keyword using parent selector syntax `\u0026` which is popular in CSS pre-processors or as a string if selector is not a valid keyword e.g. `\u0026:nth-child(4)`.\n\n```clojure\n(defstyles button [bg]\n  {:font-size \"14px\"\n   :background-color blue\n   :\u0026:hover {:background-color light-blue}\n   \"\u0026:nth-child(3)\" {:color \"blue\"}})\n```\n\n### Nested selectors\n\nSometimes when you want to override library styles you may want to refer to DOM node via its class name, tag or whatever. For this purpose you can use nested CSS selectors via string key.\n\n```clojure\n(defstyles error-form []\n  {:border \"1px solid red\"\n   \".material-ui--input\" {:color \"red\"}})\n\n[:form {:class (error-form)} ;; .css-817253 {border: 1px solid red}\n (mui/Input)] ;; .css-817253 .material-ui--input {color: red}\n```\n\n### `:css` attribute\n\n`:css` attribute allows to define styles inline and still benefit from CSS-in-JS approach.\n\n_NOTE: This feature is supported only for Rum/Sablono elements_\n\n```clojure\n(def color \"#000\")\n\n[:button {:css {:color color}} \"Button\"]\n;; (js/React.createElement \"button\" #js {:className \"css-43697 vars-43697\"} \"Button\")\n```\n\n### `defkeyframes`\n\n`defkeyframes` macro expands into a function which accepts arbitrary number of arguments, injects @keyframes declaration and returns a string that is an animation name.\n\n```clojure\n(defkeyframes spin [from to]\n  {:from {:transform (str \"rotate(\" from \"deg)\")\n   :to   {:transform (str \"rotate(\" to \"deg)\")}})\n\n[:div {:style {:animation (str (spin 0 180) \" 500ms ease infinite\")}}]\n;; (js/React.createElement \"div\" #js {:style #js {:animation \"animation-43697 500ms ease infinite\"}})\n```\n\n### `font-face`\n\n`font-face` macro allows to define custom fonts via `@font-face` CSS at-rule. The macro generates CSS string and injects it at runtime. The syntax is defined in example below.\n\n_The macro supports referring to styles declaration in a separate \\*.clj namespace._\n\n```clojure\n(require '[cljss.core :refer [font-face]])\n\n(def path \"https://fonts.gstatic.com/s/patrickhandsc/v4/OYFWCgfCR-7uHIovjUZXsZ71Uis0Qeb9Gqo8IZV7ckE\")\n\n(font-face\n  {:font-family \"Patrick Hand SC\"\n   :font-style \"normal\"\n   :font-weight 400\n   :src [{:local \"Patrick Hand SC\"}\n         {:local \"PatrickHandSC-Regular\"}\n         {:url (str path \".woff2\")\n          :format \"woff2\"}\n         {:url (str path \".otf\")\n          :format \"opentype\"}]\n   :unicode-range [\"U+0100-024F\" \"U+1E00-1EFF\"]})\n```\n\n### `inject-global`\n\n`inject-global` macro allows to defined global styles, such as to reset user agent default styles. The macro generates CSS string and injects it at runtime. The syntax is defined in example below.\n\n_The macro supports referring to styles declaration in a separate \\*.clj namespace._\n\n```clojure\n(require '[cljss.core :refer [inject-global]])\n\n(def v-margin 4)\n\n(inject-global\n  {:body     {:margin 0}\n   :ul       {:list-style \"none\"}\n   \"ul \u003e li\" {:margin (str v-margin \"px 0\")}})\n```\n\n### CSS Media Queries\n\nThe syntax is specified as of [CSS Media Queries Level 4 spec](https://www.w3.org/TR/mediaqueries-4/).\n\n```clojure\n(require [cljss.core :as css])\n\n(defstyles header [height]\n  {:height     height\n   ::css/media {[:only :screen :and [:max-width \"460px\"]]\n                {:height (/ height 2)}}})\n```\n\n- Supported media types: `#{:all :print :screen :speech}`\n- Modifiers: `#{:not :only}`\n\nMore examples of a query:\n\n#### Boolean query\n\n```clojure\n[[:monochrome]]\n```\n\n#### Simple conditional query\n\n```clojure\n[[:max-width \"460px\"]]\n```\n\n#### Multiple (comma separated) queries\n\n```clojure\n[[:screen :and [:max-width \"460px\"]]\n [:print :and [:color]]]\n```\n\n#### Basic range query\n\n_Supported operators for range queries_ `#{'= '\u003c '\u003c= '\u003e '\u003e=}`\n\n```clojure\n'[[:max-width \u003e \"400px\"]]\n```\n\n#### Complex range query\n\n```clojure\n'[[\"1200px\" \u003e= :max-width \u003e \"400px\"]]\n```\n\n## Usage\n\n`(defstyles name [args] styles)`\n\n- `name` name of a var\n- `[args]` arguments\n- `styles` a hash map of styles definition\n\n```clojure\n(ns example.core\n  (:require [cljss.core :refer-macros [defstyles]]))\n\n(defstyles button [bg]\n  {:font-size \"14px\"\n   :background-color bg})\n\n[:div {:class (button \"#fafafa\")}]\n```\n\n`(defstyled name tag-name styles)`\n\n- `name` name of a var\n- `tag-name` HTML tag name as a keyword\n- `styles` a hash map of styles definition\n\nUsing [Sablono](https://github.com/r0man/sablono) templating for [React](https://facebook.github.io/react/)\n\n```clojure\n(ns example.core\n  (:require [sablono.core :refer [html]]\n            [cljss.rum :refer [defstyled]]))\n\n(defstyled Button :button\n  {:padding \"16px\"\n   :margin-top :v-margin\n   :margin-bottom :v-margin})\n\n(html\n  (Button {:v-margin \"8px\"\n           :on-click #(console.log \"Click!\")}))\n```\n\nDynamically injected CSS:\n\n```css\n.css-43697 {\n  padding: 16px;\n  margin-top: var(--css-43697-0);\n  margin-bottom: var(--css-43697-1);\n}\n.vars-43697 {\n  --css-43697-0: 8px;\n  --css-43697-1: 8px;\n}\n```\n\n## Composing styles\n\nBecause CSS is generated at compile-time it's not possible to compose styles as data, as you would normally do it in Clojure. At run-time, in ClojureScript, you'll get functions that inject generated CSS and give back a class name. Hence composition is possibly by combining together those class names.\n\n```clojure\n(defstyles margin [\u0026 {:keys [x y]}]\n  {:margin-top    y\n   :margin-bottom y\n   :margin-left   x\n   :margin-right  x})\n\n(defstyles button [bg]\n  {:padding \"8px 24px\"\n   :background-color bg})\n\n(clojure.string/join \" \" [(button \"blue\") (margin :y \"16px\")]) ;; \".css-817263 .css-912834\"\n```\n\n## Development workflow\n\nIf you see that styles are not being reloaded, add `(:require-macros [cljss.core])`, this should do the job.\n\nWhen developing with Figwheel in order to deduplicate styles between reloads it is recommended to use Figwheel's `:on-jsload` hook to clean injected styles.\n\n```clojure\n:figwheel {:on-jsload example.core/on-reload}\n```\n\n```clojure\n(ns example.core\n  (:require [cljss.core :as css]))\n\n(defn on-reload []\n  (css/remove-styles!)\n  (render-app))\n```\n\n_NOTE: don't forget that once styles were removed you have to re-inject `defkeyframes`, `font-face` and `inject-global` declarations_\n\n## Production build\n\nSet `goog.DEBUG` to `false` to enable fast path styles injection.\n\n```clojure\n{:compiler\n {:closure-defines {\"goog.DEBUG\" false}}}\n```\n\n_NOTE: production build enables fast pass styles injection which makes those styles invisible in `\u003cstyle\u003e` tag on a page._\n\n## Roadmap\n\n- Server-side rendering\n\n## Contributing\n\n- Pick an issue with `help wanted` label (make sure no one is working on it)\n- Stick to project's code style as much as possible\n- Make small commits with descriptive commit messages\n- Submit a PR with detailed description of what was done\n\n## Development\n\nA repl for the example project is provided via [lein-figwheel](https://github.com/bhauman/lein-figwheel).\n\n```\n$ cd example\n$ lein figwheel\n```\n\nIf using emacs [cider](https://github.com/clojure-emacs/cider) - you can also launch the repl using `M-x cider-jack-in-clojurescript`.\n\n## Testing\n\ncljss uses a combination of Clojure and ClojureScript tests. Clojure tests are run via `lein test` and ClojureScript tests are run via [doo](https://github.com/bensu/doo). ClojureScript tests require a valid environment in order to run - [PhantomJS](http://phantomjs.org/) being the easiest to install.\n\nOnce a valid environment is setup, ClojureScript tests can be run like so:\n\n```\n$ lein doo phantom test once\n```\n\nOr with file watching:\n\n```\n$ lein doo phantom test\n```\n\nTo run Clojure and ClojureScript tests at once use the `test-all` task:\n\n```\n$ lein test-all\n```\n\n## Supporters\n\n[\u003cimg src=\"https://avatars3.githubusercontent.com/u/636651?s=460\u0026v=4\" width=\"100px;\" /\u003e](https://github.com/brianium)\n[\u003cimg src=\"https://avatars1.githubusercontent.com/u/193187?s=400\u0026v=4\" width=\"100px;\" /\u003e](https://github.com/sherbondy)\n\n## License\n\nCopyright © 2017 Roman Liutikov\n\nDistributed under the Eclipse Public License either version 1.0 or (at\nyour option) any later version.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclj-commons%2Fcljss","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fclj-commons%2Fcljss","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclj-commons%2Fcljss/lists"}