{"id":20276901,"url":"https://github.com/cljfx/css","last_synced_at":"2025-08-22T00:31:34.616Z","repository":{"id":62431571,"uuid":"217568726","full_name":"cljfx/css","owner":"cljfx","description":"CSS: Charmingly Simple Styling for cljfx","archived":false,"fork":false,"pushed_at":"2024-03-12T20:31:31.000Z","size":107,"stargazers_count":53,"open_issues_count":0,"forks_count":1,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-12-08T14:52:12.344Z","etag":null,"topics":["cljfx","clojure","css","javafx"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cljfx.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":"vlaaad"}},"created_at":"2019-10-25T15:59:04.000Z","updated_at":"2024-10-23T18:23:05.000Z","dependencies_parsed_at":"2022-11-01T20:46:48.303Z","dependency_job_id":null,"html_url":"https://github.com/cljfx/css","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cljfx%2Fcss","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cljfx%2Fcss/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cljfx%2Fcss/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cljfx%2Fcss/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cljfx","download_url":"https://codeload.github.com/cljfx/css/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230542288,"owners_count":18242332,"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":["cljfx","clojure","css","javafx"],"created_at":"2024-11-14T13:16:07.046Z","updated_at":"2024-12-20T06:06:49.364Z","avatar_url":"https://github.com/cljfx.png","language":"Clojure","funding_links":["https://github.com/sponsors/vlaaad"],"categories":[],"sub_categories":[],"readme":"![Logo](doc/logo.png)\n\n[![Cljdoc documentation](https://cljdoc.org/badge/cljfx/css)](https://cljdoc.org/jump/release/cljfx/css) \n[![Clojars Project](https://img.shields.io/clojars/v/cljfx/css.svg)](https://clojars.org/cljfx/css)\n\nCharmingly Simple Styling for [cljfx](https://github.com/cljfx/cljfx)\n\n# Rationale\n\nJavaFX is designed to use CSS files for styling. CSS has it's own set of problems such as \nselectors unexpectedly overriding each other and having unclear priority. Because of that, \ninline styles are more predictable and, with cljfx, where styles can be described as maps, \nalso more composable.\n\nUnfortunately, CSS is unavoidable, because controls don't provide access to their internal \nnodes, and they can be targeted only with CSS selectors. What's worse, JavaFX does not \nallow loading CSS from strings or some other data structures, instead expecting an URL \npointing to a CSS file. In addition to that, CSS is not always enough for styling JavaFX \napplication: not every Node is styleable (for example, Shapes aren't). All this leads to a\nslow iteration cycle on styling and also to duplication of styling information in CSS and \ncode.\n\nCharmingly Simple Styling is a library and a set of recommendations that solve these \nproblems. Library provides a way to configure application style using clojure data \nstructures and then construct special URLs to load CSS for styling JavaFX nodes that is \nderived from the same data structures. Recommendations help setup cljfx application in \na way that allows you to rapidly iterate on styling in a live app and keep some sanity in \nthe world of CSS.\n\n# Installation and requirements\n\nLatest version on Clojars:\n\n[![cljfx/css](https://clojars.org/cljfx/css/latest-version.svg)](https://clojars.org/cljfx/css)\n\nCharmingly Simple Styling does not depend on cljfx itself, so it can be used in any JavaFX\napplication built with Clojure.\n\n# Library overview\n\nYou want to create style description, both usable from code and loadable as CSS from URL. \nTo achieve that, Charmingly Simple Styling extends JVM URLs with custom protocol — \n`cljfxcss` — that loads CSS from globally-registered style maps. CSS is generated by \nrecursively concatenating all string keys in a style map to construct selectors, at the\nsame time using keyword keys for associated selectors to construct rules.  \n\nLet's see how it looks with this walk-through:\n```clj\n(ns my-app.style\n  (:require [cljfx.css :as css]))\n\n(def style\n  (css/register ::style\n    (let [padding 10\n          text-color \"#111111\"]\n\n      ;; you can put style settings that you need to access from code at keyword keys in a\n      ;; style map and access them directly in an app\n\n      {::padding padding\n       ::text-color text-color\n\n       ;; string key \".root\" defines `.root` selector with these rules: `-fx-padding: 10;`\n\n       \".root\" {:-fx-padding padding}\n       \".label\" {:-fx-text-fill text-color\n                 :-fx-wrap-text true}\n       \".button\" {:-fx-text-fill text-color\n                  ;; vector values are space-separated\n                  :-fx-padding [\"4px\" \"8px\"]\n                  ;; nested string key defines new selector: `.button:hover`\n                  \":hover\" {:-fx-text-fill :black}}})))\n\n\n;; `css/register` registers this style map globally so it can be loaded by URL, and puts\n;; URL string in a style map at `:cljfx.css/url` key.\n\nstyle\n=\u003e {:my-app.style/padding 10,\n    :my-app.style/text-color \"#111111\",\n    \".root\" {:-fx-padding 10},\n    \".label\" {:-fx-text-fill \"#111111\", :-fx-wrap-text true},\n    \".button\" {:-fx-text-fill \"#111111\",\n               :-fx-padding [\"4px\" \"8px\"],\n               \":hover\" {:-fx-text-fill :black}},\n\n    ;; URL has stringified version of keyword in query part of URL, and a hash of a style \n    ;; map in a fragment part. Query part is used to lookup style map in a global \n    ;; registry, and fragment is used to indicate that style is changed when it's \n    ;; redefined to trigger CSS reload in JavaFX\n\n    :cljfx.css/url \"cljfxcss:?my-app.style/style#-1561130535\"}\n\n\n;; let's see how loaded CSS looks like:\n\n(println (slurp (::css/url style)))\n\n;; prints:\n;; .root {\n;;   -fx-padding: 10;\n;; }\n;; .label {\n;;   -fx-text-fill: #111111;\n;;   -fx-wrap-text: true;\n;; }\n;; .button {\n;;   -fx-text-fill: #111111;\n;;   -fx-padding: 4px 8px;\n;; }\n;; .button:hover {\n;;   -fx-text-fill: black;\n;; }\n\n\n;; Later, in app description:\n\n{:fx/type :stage\n :showing true \n :scene {:fx/type :scene\n         :stylesheets [(::css/url style)]\n         :root ...}}\n```\n\nThat's it: you define styles, register them and feed constructed URL to JavaFX. \n\n# Recommendations\n\n## Watch for changes while iterating on styles\n\nUsually styles are static during the application runtime, but when you develop application\nstyling, it's very important to see your changes immediately. To achieve that with \nCharmingly Simple Styling, you need to take 2 steps:\n- put registered style into application state, so re-registered style can be picked up on\n  next render;\n- watch for changes in registered style and update it in app state.\n\nWhen putting style in an app state, it might be useful to also put it into component \nenvironment with `fx/ext-set-env`, so you can access it easily. See \n[`ext-set-env`/`ext-get-env` section](https://github.com/cljfx/cljfx#extending-cljfx) in \ncljfx's manual. \n\nWhen you keep style `def`ed in a Var, you can just add a watch to that var that updates\nstyle in an app state to achieve instant reload. \nSee [example](examples/e01_instant_restyling.clj) — it contains a style definition and \na rich comment that you can use to start and stop watching for changes in style to \ninstantly reapply styles in an app.  \n\nThere are also 2 resources I found invaluable while iterating on application styling:\n- Official JavaFX [CSS reference](https://openjfx.io/javadoc/12/javafx.graphics/javafx/scene/doc-files/cssref.html) —\n  to see what you can style with CSS\n- [modena.css](https://gist.github.com/maxd/63691840fc372f22f470) — default CSS used by \n  JavaFX, helpful when documentation is not enough\n\n## Don't rely on priority rules\n\nCSS has confusing priority rules, which, when relied upon, usually results in CSS files \nbecoming append only with more and more overrides. In Charmingly Simple Styling, on the \nother hand, style maps are unordered, which means resulted CSS selectors are emitted in \nundefined order. That's made intentionally to promote a more reasonable approach: create \ndifferent CSS classes for different purposes and then switch between them.\n\nFor example, instead of this:\n```clojure\n;; BAD!\n\n;; style map:\n{\".notification\" {:-fx-background-color :black\n                  \"\u003e .label\" {:-fx-text-fill :gray}}\n \".danger \u003e .label\" {:-fx-text-fill :red}}\n\n;; component:\n(defn notification [{:keys [text variant]\n                     :or {variant \"info\"}}]\n  {:fx/type :v-box\n   :style-class [\"notification\" variant]\n   :children [{:fx/type :label \n               :text text}]})\n```\nYou should use this:\n```clojure\n;; GOOD!\n\n;; style map:\n{\".notification\" {:-fx-background-color :black\n                  \"-label\" {\"-info\" {:-fx-text-fill :gray}\n                            \"-danger\" {:-fx-text-fill :red}}}}\n\n;; component:\n(defn notification [{:keys [text variant]\n                     :or {variant \"info\"}}]\n  {:fx/type :v-box\n   :style-class \"notification\"\n   :children [{:fx/type :label\n               :style-class (str \"notification-label-\" variant)\n               :text text}]})\n```\n\n## Be careful with indirect children CSS selector\n\nSome selectors are very easy and straightforward to write using style maps:\n```clojure\n{\".style-class\" {:-fx-background-color :red\n                 \"\u003e .direct-child\" {:-fx-text-fill :green\n                                    \":pseudo-class\" {:-fx-text-fill :blue}}}}\n```\nThere is another type of selectors that looks ugly written that way:\n```clojure\n{\".style-class\" {:-fx-background-color :red \n                 ;; ugly string starting with space, boo!\n                 \" .indirect-child\" {:-fx-text-fill :green}}}\n```\nThis should serve as a reminder that such selectors are bad for application performance,\nsince JavaFX has to look through all parents of a every Node with class `indirect-child`\nto see if it has `style-class` class to figure out if selector applies. As JavaFX's wiki \nstates on it's [Performance Tips and Tricks](https://wiki.openjdk.java.net/display/OpenJFX/Performance+Tips+and+Tricks) \npage, you should follow these rules when doing CSS:\n- Avoid selectors that have to match against the entire set of parents\n- Use stylesheets not setStyles\n- Use pseudo-class state, not multiple style classes, for state-based styles\n\n## Prefer custom style classes\n\nIt might be tempting to use `label` class so it's applied automatically to all labels \nwithout a need to specify their style class. Unfortunately, it means that you will have to \nfight with default styles from modena.css, because it also targets `label` class. I \nthink styling is more straightforward when you set your own style class on labels and \ndon't have to worry about disabling unexpected insets or paddings.\n\nAlternatively, you can set CSS url globally with `Application/setUserAgentStylesheet`, but \nthat means you'll have to provide CSS for every element in an app. \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcljfx%2Fcss","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcljfx%2Fcss","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcljfx%2Fcss/lists"}