{"id":14990572,"url":"https://github.com/dhleong/spade","last_synced_at":"2025-08-20T19:08:14.894Z","repository":{"id":50787277,"uuid":"200585217","full_name":"dhleong/spade","owner":"dhleong","description":"A nice tool to use in the Garden","archived":false,"fork":false,"pushed_at":"2024-11-01T19:51:12.000Z","size":150,"stargazers_count":52,"open_issues_count":1,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-12T02:25:21.780Z","etag":null,"topics":["clojurescript","css","css-modules","garden"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dhleong.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2019-08-05T05:00:06.000Z","updated_at":"2024-11-28T01:04:07.000Z","dependencies_parsed_at":"2022-09-14T22:53:14.427Z","dependency_job_id":"d966072e-d09b-49cf-9132-9df58072c66d","html_url":"https://github.com/dhleong/spade","commit_stats":{"total_commits":56,"total_committers":1,"mean_commits":56.0,"dds":0.0,"last_synced_commit":"5197e54891c3e500a073a86f63954319f332a2f5"},"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dhleong%2Fspade","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dhleong%2Fspade/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dhleong%2Fspade/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dhleong%2Fspade/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dhleong","download_url":"https://codeload.github.com/dhleong/spade/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248506371,"owners_count":21115422,"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":["clojurescript","css","css-modules","garden"],"created_at":"2024-09-24T14:20:23.751Z","updated_at":"2025-04-12T02:25:30.514Z","avatar_url":"https://github.com/dhleong.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# spade [![Clojars Project](https://img.shields.io/clojars/v/net.dhleong/spade.svg?style=flat)](https://clojars.org/net.dhleong/spade) [![cljdoc](https://cljdoc.org/badge/net.dhleong/spade)](https://cljdoc.org/jump/release/net.dhleong/spade)\n\n*A nice tool to use in the Garden*\n\n## What?\n\nSpade is a lightweight [css-modules][1]-inspired CSS-in-clojurescript library.\nIt is built on top of [garden][2] for powerful, intuitive, programmatic style\ngeneration.\n\n## How?\n\nAdd to whichever dependency manager you prefer, substituting `LATEST-VERSION`\nfor the one indicated at the top of this file:\n\n```clojure\n; leiningen, shadow-cljs.edn, etc:\n[net.dhleong/spade \"LATEST-VERSION\"]\n\n; deps.edn:\n{net.dhleong/spade {:mvn/version \"LATEST-VERSION\"}}\n```\n\n### Basic usage\n\nSimilar to [Herb][3], Spade leans on functions and macros to allow you to\ndynamically generate styles. Where Herb uses macros at the call site, however,\nSpade uses them at the declaration site to enable a much richer, more intuitive\nsyntax:\n\n```clojure\n(ns co.serenity\n  (:require [spade.core :refer [defclass]]))\n\n(defclass ship-style []\n  {:background \"#999\"}\n  [:.wing {:background \"#777\"}])\n```\n\nNotice how we don't have to return a single value from our defclass, but can\ninstead return multiple statements. The first map will apply to whatever\nelement gets the class, and the rest are used for the children. The above\nwould translate naturally to garden syntax as:\n\n```clojure\n[:.ship {:background \"#999\"}\n [:.wing {:background \"#777\"}]]\n```\n\n`defclass` creates a function, which is what that empty vector is for. If you\nwanted to be able to dynamically choose the ship's wing colors, you could write:\n\n```clojure\n(defclass ship-style [wing-color]\n  {:background \"#999\"}\n  [:.wing {:background wing-color}])\n```\n\nWhen you call the function generated by `defclass`, it inserts a new `\u003cstyle\u003e`\nelement into `\u003chead\u003e` if necessary, and returns a class name. This name is\nglobally unique based on the parameters passed, so if you have many ships with\nthe same wing color, they will all share the same class name and a single\n`\u003cstyle\u003e` declaration.\n\nTo use this in reagent, you might do:\n\n```clojure\n(defn ship [wing-color]\n  [:div {:class (ship-style wing-color)}\n   [:div.wing]])\n```\n\nSince this pattern is quite common, Spade comes with a convenience:\n\n```clojure\n(ns co.serenity\n  (:require [spade.core :refer [defattrs]]))\n\n; defattrs is identical in syntax to defclass, but returns an attributes\n; map for use in hiccup-based frameworks like reagent\n(defattrs ship-attrs [wing-color]\n  {:background \"#999\"}\n  [:.wing {:background wing-color}])\n\n(defn ship [wing-color]\n  [:div (ship-attrs wing-color)\n   [:div.wing]])\n```\n\n### Global styles\n\nSometimes you need to create global styles. No problem!\n\n```clojure\n(ns co.serenity\n  (:require [spade.core :refer [defglobal]]))\n\n(defglobal window-styles\n [:body {:background \"#333\"}])\n```\n\nGlobal styles still require a unique name for hotswapping purposes, but cannot\naccept parameters. They are inserted into the DOM as soon as they are\nevaluated.\n\n### Style Composition\n\nSpade supports composing styles just like css-modules:\n\n```clojure\n(defclass stealth-ship []\n  {:composes [(ship-style \"#111\")]\n   :background \"#111\"})\n```\n\nThe `:composes` key is only supported on the root element of a style. It supports\neither a single style name or a collection of style names, as shown above.\n\n### Media queries\n\nSpade supports `@media` queries in the exact same way you see them in the [garden][2] documentation:\n\n```clojure\n(defclass carrier-style []\n  (at-media {:min-width \"750px\"}\n    {:padding \"80px\"})\n  {:padding \"8px\"})\n```\n\n### Keyframes\n\nSpade even supports generating `@keyframes` just like you'd expect:\n\n```clojure\n(ns co.serenity\n  (:require [spade.core :refer [defkeyframes]]))\n\n(defkeyframes anim-frames []\n  [\"0%\" {:opacity 0}]\n  [\"100%\" {:opacity 1}])\n```\n\n`defkeyframes`, like `defclass`, generates a function that inserts the\nappropriate CSS into the DOM on-demand, and returns the animation identifier:\n\n```clojure\n(defclass serenity []\n  {:animation [[(anim-frames) \"560ms\" 'ease-in-out]]})\n```\n\n### Syntactic Sugar\n\nSpade also provides some extra syntactic sugar, performed at compile time\nfor \"zero-cost\" abstractions.\n\n#### CSS Custom Properties\n\nCSS custom properties (AKA variables) can be a convenient way to, for\nexample, define dynamic theme colors once and reuse them throughout the\ncodebase. Spade provides some extra sugar to make using them easier and\nmore idiomatic:\n\n```clojure\n(defglobal light-dark\n  (at-media {:prefers-color-scheme 'dark}\n    [\":root\" {:theme/*background* \"#000\"\n              :theme/*text* \"#E0EBFF\"}])\n  [\":root\" {:theme/*background* \"#fff\"\n            :theme/*text* \"#000\"}])\n\n(defclass page []\n  {:background :theme/*background*\n   :color :theme/*text*})\n```\n\nNotice how our declaration and usage sites are identical, and that\nthey're \"just\" normal keywords. However, by using `*earmuffs*` around\nthe name of the keyword, Spade knows that it is meant to be a variable,\nand applies the correct CSS styling based on the position within the\nstyle. This naming was chosen because it is reminiscent of the naming\nof dynamic Clojure vars, and because `*` is not valid in a CSS property\nname, so the meaning is unambiguous.\n\nNormal keyword namespace semantics apply, so you can expect that\n`:theme/*text*` and `:crew.quarters/*text*` will result in distinct\nvariables. In fact, you don't need any namespace at all; `:*text*` will\nalso result in a perfectly valid CSS variable, disinct from any of the\nother two mentioned above.\n\nThe above example will generate the following CSS:\n\n```css\n@media (prefers-color-scheme: dark) {\n  :root {\n    --theme--background: #000;\n    --theme--text: #E0EBFF;\n  }\n}\n\n:root {\n  --theme--background: #fff;\n  --theme--text: #000;\n}\n\n.page {\n  background: var(--theme--background);\n  color: var(--theme--text);\n}\n```\n\nNote that [CSS Custom Properties are not supported on all browsers][4], and this syntax compiles to that feature directly without any attempt at backwards compatibility—if CSS Custom Properties are not supported on a browser you are targetting, this syntax will also not be supported.\n\n## Development\n\nTo get an interactive development environment run:\n\n    lein figwheel\n\nand open your browser at [localhost:3449](http://localhost:3449/).\nThis will auto compile and send all changes to the browser without the\nneed to reload. After the compilation process is complete, you will\nget a Browser Connected REPL. An easy way to try it is:\n\n    (js/alert \"Am I connected?\")\n\nand you should see an alert in the browser window.\n\n## License\n\nCopyright © 2019-2024 Daniel Leong\n\nDistributed under the Eclipse Public License either version 1.0\n\n[1]: https://github.com/css-modules/css-modules\n[2]: https://github.com/noprompt/garden/\n[3]: https://github.com/roosta/herb\n[4]: https://caniuse.com/css-variables\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdhleong%2Fspade","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdhleong%2Fspade","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdhleong%2Fspade/lists"}