One of the things that I want from css is to have to possibility to compose already defined styles, to define new ones…
If you try to write “Semantic CSS”, you will find a hard time trying to avoid the repetition on things that look the same but are different things, like when you want to render a “card” for the an author and then for an article.
You can create “content agnostic CSS components”, but things start to get complicated, when you want to avoid duplication if things from one component are similar to other components.
One way of solving this problems is by using something like the @extend
function from sass, or the @apply
function from Tailwind css, but both tools
are recommended just for some specific cases and not to build your styles on
top of them (@extend, @apply).
This is why I have been working on this Styler, a tool to compose css classes from other classes an groups of classes.
Is a tool, to extend what the utility css classes can give us. A tool to build on top of frameworks like Tachyons or Tailwind css.
You can declare styles with a name and an already defined css class, for example if you define a style
named btn
, like this…
styles = Styler.new do
style :btn, ["white", "bg_blue"]
end
That will return an object where you can call btn
on it…
styles.btn.to_s # => "white bg_blue"
You would be able to use it like this in your erb files…
<button class="<%= styles.btn %>">My button</button>
or in haml…
%button{class: styles.btn} My button
To output…
<button class="white bg_blue">My button</button>
You can define many of this styles and compose them…
styles = Styler.new do
style :btn, ["padding3", "marin3"]
style :blue_btn, [btn, "white", "bg_blue"]
end
%button{class: styles.btn} My button
%button{class: styles.blue_btn} My button
And the output would be…
<button class="padding3 margin3">My button</button>
<button class="padding3 margin3 white bg_blue">My button</button>
By composing your styles you can also substract classes from previous styles, like this…
styles = Styler.new do
style :default, ["pa3", "white", "bg_blue"]
style :danger, [default - "bg_blue", "bg_red"]
end
styles.default.to_s # => "pa3 white bg_blue"
styles.danger.to_s # => "pa3 white bg_red"
You can also define styles that expect an argument, to help you decide which styles to display, like this…
styles = Styler.new do
style :default_color do |project|
if project[:color] == "blue"
["bg_blue"]
else
["bg_red"]
end
end
end
project = { color: "blue" }
styles.default_color(project).to_s # => "bg_blue"
And you can use this styles to build other styles, like this…
project = { color: "blue" }
styles = Styler.new do
style :default_color do |project|
if project[:color] == "blue"
["bg_blue"]
else
["bg_red"]
end
end
style :title, [default_color(project), "pa3"]
end
styles.title(project).to_s # => "bg_blue pa3"
Or like this…
styles = Styler.new do
style :default_color do |project|
if project[:color] == "blue"
["bg_blue"]
else
["bg_red"]
end
end
style :title do |project|
[default_color(project), "pa3"]
end
end
project = { color: "blue" }
styles.title(project).to_s # => "bg_blue pa3"
You can define collections as namespaces for your styles…
styles = Styler.new do
collection :buttons do
style :default, ["pa3", "blue"]
style :danger, [default - "blue", "red"]
end
end
styles.respond_to?(:default) # => false
styles.respond_to?(:danger) # => false
styles.buttons.default.to_s # => "pa3 blue"
styles.buttons.danger.to_s # => "pa3 red"
You can define nested collections to build complete themes…
styles = Styler.new do
collection :v1 do
collection :buttons do
style :default, ["pa3", "blue"]
end
end
collection :v2 do
collection :buttons do
style :default, ["pa3", "red"]
end
end
end
styles.v1.buttons.default.to_s # => "pa3 blue"
styles.v2.buttons.default.to_s # => "pa3 red"
Like with the styles, you can define collections that require arguments, like this..
styles = Styler.new do
collection :buttons do |project|
if project[:color] == "blue"
style :default, ["pa3", "blue"]
else
style :default, ["pa3", "red"]
end
end
end
project = { color: "blue" }
styles.buttons(project).default.to_s # => "pa3 blue"
And you can pick one of those collections, by using a collection_alias
…
styles = Styler.new do
collection :v1 do
collection :buttons do
style :default, ["pa3", "blue"]
end
end
collection :v2 do
collection :buttons do
style :default, ["pa3", "red"]
end
end
collection_alias :theme, v1
end
styles.theme.buttons.default.to_s # => "pa3 blue"
If you need you can select the collection alias dynamically with a block…
styles = Styler.new do
collection :v1 do
collection :buttons do
style :default, ["pa3", "blue"]
end
end
collection :v2 do
collection :buttons do
style :default, ["pa3", "red"]
end
end
collection_alias :theme do |current_version|
if current_version == "v1"
v1
else
v2
end
end
end
styles.theme("v1").buttons.default.to_s # => "pa3 blue"
styles.theme("v2").buttons.default.to_s # => "pa3 red"
You can also define your collections on different “stylers” and be able to declare an alias to access them…
v1 = Styler.new do
collection :buttons do
style :default, ["pa3", "blue"]
end
end
v2 = Styler.new do
collection :buttons do
style :default, ["pa3", "red"]
end
end
styles = Styler.new do
collection_alias :theme do |current_version|
if current_version == "v1"
v1
else
v2
end
end
end
styles.theme("v1").buttons.default.to_s # => "pa3 blue"
styles.theme("v2").buttons.default.to_s # => "pa3 red"
If you need to use the styles from other collection, but you need to override some of them, you can copy the styles and then override what you want.
styles = Styler.new do
collection :v1 do
collection :buttons do
style :default, ["pa3", "blue"]
style :danger, [default - "blue", "red"]
end
end
collection :v2 do
collection :buttons do
copy_styles_from collection: v1.buttons
style :danger, [v1.buttons.danger - "red", "orange"]
end
end
end
expect(styles.v2.buttons.default.to_s).to eq "pa3 blue"
expect(styles.v2.buttons.danger.to_s).to eq "pa3 orange"
If you want to learn more you can read the source on github, and maybe take a look a the specs to find more examples.
I have been working on this tool for some weeks, and I have been using it on some personal projects and to build a collection with the tachyons components.
I have pitched the library to my team at briq.mx (we use Tachyons), but we are not using it yet…
Is still a concept, and would be nice to receive some feedback from you!
If you want to play with this tool in a project you can install it by adding this line to your application’s Gemfile:
gem "ruby_styler"
And then execute:
$ bundle install
Or install it yourself as:
$ gem install styler
Here I try to share knowledge and fixes to common problems and struggles for ruby on rails developers, like How to fetch the latest-N-of-each record or How to test that an specific mail was sent or a Capybara cheatsheet. You can see more examples on Most recent posts or all posts.