{"id":17279214,"url":"https://github.com/mainej/headlessui-reagent","last_synced_at":"2025-10-18T09:05:20.713Z","repository":{"id":43653924,"uuid":"359655089","full_name":"mainej/headlessui-reagent","owner":"mainej","description":"reagent wrappers for @headlessui/react components","archived":false,"fork":false,"pushed_at":"2022-07-18T17:10:51.000Z","size":165,"stargazers_count":37,"open_issues_count":2,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-14T08:48:15.624Z","etag":null,"topics":["headlessui","reagent","tailwind","tailwindcss"],"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/mainej.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-04-20T02:08:42.000Z","updated_at":"2024-03-11T16:27:22.000Z","dependencies_parsed_at":"2022-08-22T17:50:59.386Z","dependency_job_id":null,"html_url":"https://github.com/mainej/headlessui-reagent","commit_stats":null,"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"purl":"pkg:github/mainej/headlessui-reagent","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mainej%2Fheadlessui-reagent","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mainej%2Fheadlessui-reagent/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mainej%2Fheadlessui-reagent/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mainej%2Fheadlessui-reagent/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mainej","download_url":"https://codeload.github.com/mainej/headlessui-reagent/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mainej%2Fheadlessui-reagent/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279500019,"owners_count":26180879,"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-10-18T02:00:06.492Z","response_time":62,"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":["headlessui","reagent","tailwind","tailwindcss"],"created_at":"2024-10-15T09:16:30.577Z","updated_at":"2025-10-18T09:05:20.685Z","avatar_url":"https://github.com/mainej.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# headlessui-reagent\n\nReagent wrappers for [@headlessui/react][headlessui], bringing accessible,\nkeyboard-friendly, style-agnostic UI components to Reagent and re-frame\nprojects.\n\n## Installation\n\nInstall as a [Clojure dependency][clojars]. Assuming you run your project with\nshadow-cljs, `@headlessui/react` will be installed as a JS dependency.\nOtherwise, you may have to install it yourself with npm/yarn. \n\nSince `v1.4.0.32`, `headlessui-reagent` tracks `@headlessui/react`'s versioning.\nThat is, the first three segments of the version (`1.4.0`) indicate that this\nlibrary was built with `@headlessui/react` version `1.4.0`. The last segment\n(`32`) distinguishes between releases of this library that were all built with\nthe same version of `@headlessui/react`.\n\nIf for some reason you need an earlier version, both `v1.2.0` and `v1.2.1` of\n`headlessui-reagent` were built with `@headlessui/react` `1.2.0`. Earlier\nreleases were built with `@headlessui/react` `1.0.0`.\n\n## Usage\n\nUsage follows the Headless UI API. For example, to use a\n[Disclosure][headlessui-disclosure] in Reagent, with Tailwind CSS (though any\nstyling system will work):\n\n```clojure\n(require '[headlessui-reagent.core :as ui])\n\n[ui/disclosure\n  [ui/disclosure-button {:class [:w-full :px-4 :py-2 :text-sm :font-medium :text-purple-900 :bg-purple-100 :rounded-lg]}\n    \"Explain\"]\n  [ui/disclosure-panel {:class [:px-4 :pt-4 :pb-2 :text-sm :text-gray-500]}\n    [:p \"Some explanation.\"]]]\n```\n\nTo see each of the components in action, check out the [examples](/example).\n\n### Styling the active item\n\nTo conditionally apply markup or styles based on the component's state, you have\na few choices.\n\nFirst, if you're using Headless UI with Tailwind CSS, as many people do,\nconsider using the\n[@headlessui/tailwindcss](https://github.com/tailwindlabs/headlessui/tree/main/packages/%40headlessui-tailwindcss)\nplugin. That'll let you target specific UI states with plain CSS classes.\n\n```clojure\n[ui/menu\n  [ui/menu-button \"More\"]\n  [ui/menu-items\n   [ui/menu-item\n    [:a.ui-active:bg-blue-500.ui-active:text-white.ui-not-active:bg-white.ui-not-active:text-black\n     {:href \"/account-settings\"}\n     \"Account Settings\"]]\n   ,,,]]\n```\n\nFor a more programmatic approach, Headless UI provides [\"render\nprops\"][render-props]. If the Reagent component is given a single function as a\nchild, the function is called with a hash map of render props (e.g. `:open` for\na Disclosure). The return value of the function, which should be a single\n(hiccup-style) component, will be rendered.\n\n```clojure\n[ui/disclosure\n (fn [{:keys [open]}]\n   [:\u003c\u003e\n    [ui/disclosure-button (if open \"Hide\" \"Show\")]\n    [ui/disclosure-panel ,,,]])]\n```\n\nIf you need to control the CSS classes only, not the component's contents,\n`:class` can be a function which will receive the render props:\n\n```clojure\n[ui/disclosure-button {:class (fn [{:keys [open]}]\n                                [:border (when open :bg-blue-200)])}\n \"Show more\"]\n```\n\n### Rendering a different element for a component\n\nMany Headless UI components accept an `\"as\"` prop, which controls how they are\nrendered into the dom. If the corresponding Reagent component is given an `:as`\nprop, it can be any hiccup-style component: a string, a keyword or a function\nwhich returns hiccup.\n\nIf `:as` is a full-fledged Reagent component (i.e. a function which returns\nhiccup), then that component must accept two arguments, its properties and its\nchildren:\n\n```clojure\n(defn panel-ul [props children]\n  (into [:ul.bg-red-500 props] children))\n\n[ui/disclosure-panel {:as panel-ul}\n  [:li \"Note this.\"]\n  [:li \"This too.\"]]\n```\n\nThe props will contain ARIA attributes, event handlers and other attributes\nnecessary for the `:as` component to work correctly, so you must use them.\n\nThe above example is so simple it would be more easily written as:\n\n```clojure\n[ui/disclosure-panel {:as :ul.bg-red-500}\n  [:li \"Note this.\"]\n  [:li \"This too.\"]]\n```\n\nOr, closest to the Headless UI style, as:\n\n```clojure\n[ui/disclosure-panel {:as \"ul\", :class [:bg-red-500]}\n  [:li \"Note this.\"]\n  [:li \"This too.\"]]\n```\n\n### Picking an item\n\nThe Listbox, RadioGroup and Combobox components are designed to assist in\npicking an item from a list of items. Headless UI coordinates this by having a\nroot element and several child elements. The child elements each have a `value`,\nto identify themselves. The root element also has a `value` for the currently\nselected item and an `onChange` handler that is called with a different `value`\nwhen another item is selected.\n\nIn this library, these correspond to the `:value` and `:on-change` attributes.\nThe `:value` must be a JavaScript object, and similarly the argument to the\n`:on-change` callback will be a JavaScript object. The library does not\nautomatically convert between JavaScript and ClojureScript.\n\nThis begs the question, what's the best way to use these components when the\nitems are ClojureScript objects? The trick is to ensure that the items each have\na unique identifier that is a JavaScript primitive (numbers and strings are\npreferred because they are primitives in both ClojureScript and JavaScript). We\nuse this unique identifier as the item's `:value`. When a new item is selected,\n`:on-change` will be called with the primitive identifier. At that point, we're\nback in ClojureScript code, so we can use the identifier to lookup the full\nitem.\n\nHere's an example. Notice that `:id`, an integer, is used as the `:value` in\nboth the `ui/listbox` and `ui/listbox-option`. That is, to Headless UI, it's\nassisting in picking an integer. That integer is converted back into a full\nperson in `:on-change`.\n\n```clojure\n(def people [{:id 1, :name \"Wade Cooper\"}\n             {:id 2, :name \"Arlene Mccoy\"}\n             {:id 3, :name \"Devon Webb\"}\n             {:id 4, :name \"Tom Cook\"}\n             {:id 5, :name \"Tanya Fox\"}\n             {:id 6, :name \"Hellen Schmidt\"}])\n\n(def person-by-id (zipmap (map :id people) people))\n\n(reagent.core/with-let [!selected (reagent.core/atom (first people))]\n  (let [selected @!selected]\n    [ui/listbox\n     {:value     (:id selected)\n      :on-change #(reset! !selected (get person-by-id %1))}\n     [ui/listbox-button (:name selected)]\n     [ui/listbox-options\n      (for [person people]\n        ^{:key (:id person)}\n        [ui/listbox-option\n         {:value (:id person)}\n         (:name person)])]]))\n```\n\n## Known bugs\n\nThere are some known limitations to the interop between Reagent and Headless UI.\nBug fixes welcome!\n\n### Rendering children directly\n\nIn some cases where Headless UI would usually render a wrapper element, it\npermits rendering the children directly instead, by passing `React.Fragment` as\n`\"as\"`. This is not supported because Headless UI and Reagent fail to convey\nprops between them.\n\n```clojure\n;; DON'T do this\n[ui/menu-button {:as :\u003c\u003e}\n  [:button.block {:type \"button\"} \"Open\"]]\n```\n\n## Acknowledgements\n\nIf you'd like to support Tailwind Labs, the creators of Headless UI, consider\nsigning up for [Tailwind UI][tailwind-ui]. You'll get access to hundreds of UI\ncomponents, many of which use Headless UI, and which can be adapted to work with\nheadlessui-reagent.\n\nheadlessui-reagent isn't an official part of Headless UI, and I don't get\nanything when you sign up for Tailwind UI. I just believe that Tailwind CSS and\nHeadless UI are a natural fit for Reagent/Hiccup projects, and want to see them\nthrive.\n\n## License\n\nCopyright © 2022 Jacob Maine\n\nDistributed under the MIT License.\n\n[render-props]: https://reactjs.org/docs/render-props.html\n[headlessui]: https://headlessui.dev/\n[headlessui-disclosure]: https://headlessui.dev/react/disclosure\n[clojars]: https://clojars.org/com.github.mainej/headlessui-reagent\n[tailwind-ui]: https://tailwindui.com/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmainej%2Fheadlessui-reagent","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmainej%2Fheadlessui-reagent","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmainej%2Fheadlessui-reagent/lists"}