{"id":13878358,"url":"https://github.com/avo-hq/class_variants","last_synced_at":"2025-05-16T03:04:46.881Z","repository":{"id":62176825,"uuid":"558563814","full_name":"avo-hq/class_variants","owner":"avo-hq","description":"Easily configure styles and apply them as classes.","archived":false,"fork":false,"pushed_at":"2025-04-07T11:12:29.000Z","size":145,"stargazers_count":147,"open_issues_count":5,"forks_count":6,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-05-13T06:58:42.323Z","etag":null,"topics":["rails","ruby","tailwindcss"],"latest_commit_sha":null,"homepage":"","language":"Ruby","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/avo-hq.png","metadata":{"files":{"readme":"readme.md","changelog":"CHANGELOG.md","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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["adrianthedev"]}},"created_at":"2022-10-27T20:02:24.000Z","updated_at":"2025-05-08T08:14:54.000Z","dependencies_parsed_at":"2024-08-21T21:40:46.119Z","dependency_job_id":"3fffedc0-b273-4e54-b1e7-2e02775fc130","html_url":"https://github.com/avo-hq/class_variants","commit_stats":{"total_commits":34,"total_committers":5,"mean_commits":6.8,"dds":0.5588235294117647,"last_synced_commit":"f04e762ea028bfe517122f587d82aed00ac7f412"},"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avo-hq%2Fclass_variants","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avo-hq%2Fclass_variants/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avo-hq%2Fclass_variants/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avo-hq%2Fclass_variants/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/avo-hq","download_url":"https://codeload.github.com/avo-hq/class_variants/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254459088,"owners_count":22074605,"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":["rails","ruby","tailwindcss"],"created_at":"2024-08-06T08:01:47.231Z","updated_at":"2025-05-16T03:04:41.872Z","avatar_url":"https://github.com/avo-hq.png","language":"Ruby","funding_links":["https://github.com/sponsors/adrianthedev"],"categories":["Ruby"],"sub_categories":[],"readme":"# Class variants\n\nWe ❤️ Tailwind CSS but sometimes it's difficult to manage the state of some elements using conditionals. `class_variants` is a tiny helper that should enable you to create, configure, and apply different variants of elements as classes.\n\nInspired by [variant-classnames](https://github.com/mattvalleycodes/variant-classnames) ✌️\n\n## Quicklinks\n\n* [DRY up your tailwind CSS using this awesome gem](https://www.youtube.com/watch?v=cFcwNH6x77g)\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'class_variants'\n```\n\nAnd then execute:\n\n```\n$ bundle\n```\n\nOr install it yourself as:\n\n```\n$ gem install class_variants\n```\n\n## Usage\n\nWe create an object from the class or helper where we define the configuration using four arguments:\n\n1. The `base` keyword argument with default classes that should be applied to each variant.\n2. The `variants` keyword argument where we declare the variants with their option and classes.\n3. The `compound_variants` keyword argument where we declare the compound variants with their conditions and classes\n4. The `defaults` keyword argument (optional) where we declare the default value for each variant.\n\nBelow we'll implement the [button component](https://tailwindui.com/components/application-ui/elements/buttons) from Tailwind UI.\n\n```ruby\n# Define the variants and defaults\nbutton_classes = ClassVariants.build(\n  base: \"inline-flex items-center rounded border border-transparent font-medium text-white hover:text-white shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2\",\n  variants: {\n    color: {\n      indigo: \"bg-indigo-600 hover:bg-indigo-700 focus:ring-indigo-500\",\n      red: \"bg-red-600 hover:bg-red-700 focus:ring-red-500\",\n      blue: \"bg-blue-600 hover:bg-blue-700 focus:ring-blue-500\",\n    },\n    size: {\n      sm: \"px-2.5 py-1.5 text-xs\",\n      md: \"px-3 py-2 text-sm\",\n      lg: \"px-4 py-2 text-sm\",\n      xl: \"px-4 py-2 text-base\",\n    },\n    compound_variants: [\n      { color: :red,  border: true, class: \"border-red-800\"  },\n      { color: :blue, border: true, class: \"border-blue-800\" }\n    ]\n    # A variant whose value is a string will be expanded into a hash that looks\n    # like  { true =\u003e \"classes\" }\n    icon: \"w-full justify-center\",\n    # Unless the key starts with !, in which case it will expand into\n    # { false =\u003e \"classes\" }\n    \"!icon\": \"w-auto\",\n  },\n  defaults: {\n    size: :md,\n    color: :indigo,\n    icon: false\n  }\n)\n\n# Call it with our desired variants\nbutton_classes.render(color: :blue, size: :sm)\nbutton_classes.render\nbutton_classes.render(color: :red, size: :xl, icon: true)\n```\n\n## Compound Variants\n\n```ruby\nbutton_classes = ClassVariants.build(\n  base: \"inline-flex items-center rounded\",\n  variants: {\n    color: {\n      red:  \"bg-red-600\",\n      blue: \"bg-blue-600\",\n    },\n    border: \"border\"\n  },\n  compound_variants: [\n    { color: :red,  border: true, class: \"border-red-800\"  },\n    { color: :blue, border: true, class: \"border-blue-800\" }\n  ]\n)\n\nbutton_classes.render(color: :red) # =\u003e \"inline-flex items-center rounded bg-red-600\"\nbutton_classes.render(color: :red, border: true) # =\u003e \"inline-flex items-center rounded bg-red-600 border border-red-800\"\n```\n\n## Override classes with `render`\n\nWe can also override the builder classes in the `render` method.\n\n```ruby\nbutton_classes = ClassVariants.build(\n  base: \"inline-flex items-center rounded\",\n  variants: { ... },\n)\n\nbutton_classes.render(color: :red, class: \"block\")\n```\n\nNow, the `block` class will be appended to the classes bus.\n\nIf you're using the [`tailwind_merge`](#tailwind_merge) plugin it will override the `inline-flex` class.\n\n## Block configurations\n\nYou might have scenarios where you have more advanced conditionals and you'd like to configure the classes using the block notation.\n\n```ruby\nalert_classes = ClassVariants.build do\n  # base\n  base \"...\"\n\n  # variant\n  variant color: :red, class: \"...\"\n\n  # compound variant\n  variant type: :button, color: :red, class: \"...\"\n\n  # defaults\n  defaults color: :red, type: :button\nend\n\n# usage\nalert_classes.render(color: :red, type: :button)\n```\n\n## Slots\n\nYou might have components which have multiple slots or places where you'd like to use conditional classes.\n`class_variants` supports that through slots.\n\n```ruby\n# Example\n\nalert_classes = ClassVariants.build do\n  # base with slots\n  base do\n    slot :head, class: \"...\"\n    slot :body, class: \"...\"\n  end\n\n  # variant with slots\n  variant color: :red do\n    slot :head, class: \"...\"\n    slot :body, class: \"...\"\n  end\n\n  # compound variant with slots\n  variant type: :button, color: :red do\n    slot :head, class: \"...\"\n    slot :body, class: \"...\"\n  end\n\n  # set defaults\n  defaults color: :red, type: :button\nend\n```\n\n```erb\n\u003cdiv\u003e\n  \u003cdiv class=\"\u003c%= alert_classes.render(:head) %\u003e\"\u003e\n    Head of alert\n  \u003c/div\u003e\n  \u003cdiv class=\"\u003c%= alert_classes.render(:body) %\u003e\"\u003e\n    Body of alert\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\n## Merge definitions\n\n```ruby\nalert_classes = ClassVariants.build(base: \"bg-white\")\nalert_classes.merge(base: \"text-black\")\nalert_classes.render # =\u003e \"bg-white text-black\"\n```\n\n## Full API\n\n```ruby\n# Configuration\nalert_classes = ClassVariants.build(\n  base: \"...\",\n  variants: {\n    color: {\n      red: \"...\",\n      black: \"...\"\n    },\n    type: {\n      button: \"...\",\n      link: \"...\"\n    }\n  },\n  compound_variants: [],\n  defaults: {\n    color: :red,\n    type: :button\n  }\n) do\n  # base without slots\n  base \"...\"\n\n  # base with slots\n  base do\n    slot :head, class: \"...\"\n    slot :body, class: \"...\"\n  end\n\n  # variant without slots\n  variant color: :red, class: \"...\"\n\n  # variant with slots\n  variant color: :red do\n    slot :head, class: \"...\"\n    slot :body, class: \"...\"\n  end\n\n  # compound variant without slots\n  variant type: :button, color: :red, class: \"...\"\n\n  # compound variant with slots\n  variant type: :button, color: :red do\n    slot :head, class: \"...\"\n    slot :body, class: \"...\"\n  end\n\n  # option 1 (my favorite)\n  defaults color: :red, type: :button\n\n  # option 2\n  defaults do\n    color :red\n    type :button\n  end\nend\n\n# Usage\n\n# renders the defaults\nalert_classes.render\n\n# render default slot with custom variants\nalert_classes.render(color: :red)\n\n# render slot with defaults variants\nalert_classes.render(:body)\n\n# render slot with custom variants\nalert_classes.render(:body, color: :red)\n\n# if slot not exist, throw error? return empty classes?\nalert_classes.render(:non_existent_slot, color: :red)\n\n# render default slot with custom class (will be merged)\nalert_classes.render(class: \"...\")\n\n# render slot with custom class (will be merged)\nalert_classes.render(:body, class: \"...\")\n```\n\n## Use with Rails\n\n```ruby\n# Somewhere in your helpers\ndef button_classes\n  class_variants(\n    base: \"inline-flex items-center rounded border border-transparent font-medium text-white hover:text-white shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2\",\n    variants: {\n      size: {\n        sm: \"px-2.5 py-1.5 text-xs\",\n        md: \"px-3 py-2 text-sm\",\n        lg: \"px-4 py-2 text-sm\",\n        xl: \"px-4 py-2 text-base\",\n      },\n      color: {\n        indigo: \"bg-indigo-600 hover:bg-indigo-700 focus:ring-indigo-500\",\n        red: \"bg-red-600 hover:bg-red-700 focus:ring-red-500\",\n        blue: \"bg-blue-600 hover:bg-blue-700 focus:ring-blue-500\",\n      },\n    },\n    defaults: {\n      size: :md,\n      color: :indigo,\n    }\n  )\nend\n```\n\n```erb\n\u003c!-- In your views --\u003e\n\u003c%= link_to :Avo, \"https://avohq.io\", class: button_classes.render(color: :blue, size: :sm) %\u003e\n\u003c%= link_to :Avo, \"https://avohq.io\", class: button_classes.render %\u003e\n\u003c%= link_to :Avo, \"https://avohq.io\", class: button_classes.render(color: :red, size: :xl) %\u003e\n```\n\n### Output\n\n### ![](sample.jpg)\n\n## Helper module\n\nIf you're developing something more complex you might want to use composition more. You might want to use the helper module for that.\n\n```ruby\nclass MyClass\n  include ClassVariants::Helper\n\n  class_variants(\n    base: \"bg-white\",\n    variants: {\n      color: {\n        red: \"text-red\",\n        blue: \"text-blue\"\n      }\n    }\n  )\nend\n\nMyClass.new.class_variants(color: :red, class: \"shadow\") # =\u003e \"bg-white text-red shadow\"\n```\n\nThis helper supports class inheritance, so that the subclass receives a copy of the class_variants config that the parent class had at the time of inheritance. From that point on, the settings are kept separate for both. Successive calls to class_variants helper method, will cause the configuration to be merged.\n\n```ruby\nclass A\n  include ClassVariants::Helper\n\n  class_variants(base: \"bg-red\")\nend\n\nclass B \u003c A\n  class_variants(base: \"text-black\")\nend\n\nA.class_variants(base: \"text-white\")\n\nA.new.class_variants # =\u003e \"bg-red text-white\"\nB.new.class_variants # =\u003e \"bg-red text-black\"\n```\n\n## `tailwind_merge`\n\nBy default, the classes are merged using `concat`, but you can use the awesome [TailwindMerge](https://github.com/gjtorikian/tailwind_merge) gem.\nInstall the gem using `bundle add tailwind_merge` and use this configuration to enable it. If you're using Rails, you can put this in an initializer.\n\n```ruby\nClassVariants.configure do |config|\n  merger = TailwindMerge::Merger.new\n  config.process_classes_with do |classes|\n    merger.merge(classes)\n  end\nend\n```\n\n## Other packages\n\n- [`active_storage-blurhash`](https://github.com/avo-hq/active_storage-blurhash) - A plug-n-play [blurhash](https://blurha.sh/) integration for images stored in ActiveStorage\n- [`avo`](https://github.com/avo-hq/avo) - Build Content management systems with Ruby on Rails\n- [`prop_initializer`](https://github.com/avo-hq/prop_initializer) - A flexible tool for defining properties on Ruby classes.\n- [`stimulus-confetti`](https://github.com/avo-hq/stimulus-confetti) - The easiest way to add confetti to your StimulusJS app\n\n## Try Avo ⭐️\n\nIf you enjoyed this gem try out [Avo](https://github.com/avo-hq/avo). It helps developers build Internal Tools, Admin Panels, CMSes, CRMs, and any other type of Business Apps 10x faster on top of Ruby on Rails.\n\n[![](./logo-on-white.png)](https://github.com/avo-hq/avo)\n\n## Articles\n\n[TIL: How to use `class_variants` with Phlex](https://henrikbjorn.medium.com/til-how-to-use-class-variants-with-phlex-8042bd4407f1)\n\n## Contributing\n\n1. Fork it `git clone https://github.com/avo-hq/class_variants`\n2. Create your feature branch `git checkout -b my-new-feature`\n3. Commit your changes `git commit -am 'Add some feature'`\n4. Push to the branch `git push origin my-new-feature`\n5. Create new Pull Request\n\n## License\n\nThis package is available as open source under the terms of the MIT License.\n\n## Cutting a release\n\n```bash\n# Build\ngem build class_variants.gemspec -o latest.gem\n# Publish\ngem push --host https://rubygems.org/ ./latest.gem\n# Cut a tag\ngit tag v0.0.6 -a -m \"Version 0.0.6\"\n# Push tag to repo\ngit push --follow-tags\n# Go to the repo and generate release from tag\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Favo-hq%2Fclass_variants","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Favo-hq%2Fclass_variants","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Favo-hq%2Fclass_variants/lists"}