pepicrft

pepicrft

Styled components in Phoenix?

Hi :wave:

I would like to implement a solution like styled-components for Phoenix components such that developers can write their styles using Elixir and the CSS would be generated at compile time. The API would be something along the lines of:

defmodule Components do
  use Phoenix.Component

  style background_color: "red"
  def my_component() do
    ~H"""
    <div class={@styled_class}>
    Hello world
    </div>
    """
  end
end

Because I relatively new to Elixir macros and the compilation process, I’m struggling to map the API to an actual implementation. I was thinking about making style a macro that uses module attributes to store the styles of the components, similar to how attr does, and then have a custom compiler that combines the styles for all the components in the project and outputs a CSS into a configured path.

One piece I don’t know how to do without a sigil that wraps ~H is passing the @styled_class assign. I was thinking about style writing an attr attribute that uses the default: as a way to set the actual value.

Do you think the implementation is sensible? If not, what would you do differently?

I know Tailwind is proposed as the default in Phoenix, but I’m not a huge fan of its mental models so I’m exploring a solution that align with my mental models.

Most Liked

fceruti

fceruti

Hey Pedro,

I’ve been working on a somewhat similar solution to what you are proposing here. I was planning on open sourcing it once it was a little bit more polished, but the intention from the beginning was to open source it. I’m open on giving access to interested parties now.

The basic architecture is composed of 3 parts: Components, Nomenclators & Hydrators.

Components

Components are a super set of phoenix components with two additional macros: variant and classes (well, also component, I couldn’t override def, as phoenix already does that).

These two macros are used to describe how the component can change depending on either code calls aka, size={:sm} or what class name are to be used for an inner element. Which brings me to the next part

Nomenclators

Nomenclators responsability is to give class names to components and it’s sub elements.

This is the default nomenclator, that spits classes like btn btn-sm btn-outline btn-primary. If you check out the code, class_name/3 can be either _component, _variant, _option, used to variant class names or _component, :classes, _class_name that gives names to classes macros defined in the component.

You can see how just with this, you could just add tailwind inline classes and forget about the next part:

Hydrators

Hydrators responsability is to Hydrate with css the skeleton that nomenclators create. They are auto generated based on: the apps it should go look for components, the parent hydrator and a nomenclator.

This is a very early stage of the DefaultHydrator that users will be able to use as a base.

Next is the auto generated version of a hydrator that has DefaultHydrator as it’s parent.

A few things to note here:

  • Notice how it brings all the style and var code as comments from the parent (and it’s parent parent), so that you don’t need to go back and forth trying to understand why styles are the way they are.
  • variables can be then injected into ~CSS sigils
  • If you modify a sub hydrator the engine is smart enough not to crush the changes. Hydrators are constantly being regenerated.
  • ~CSS sigils sorts and properly indents properties with a custom formatter.
  • A main focus of this is to have many many themes for my app, and reuse style code as much as possible.

Finally there are a few mix tasks to generate all the files.

Right now I’m working on two things:

  1. Not your classic component gallery. You’ll not only be able to see all components and it’s possible variations, but have complete access to all variables and css code, so you can edit them right there in your browser, and have those changes be reflected on your code and compiled to css live.
  2. Actually implement some damn components.

There are more things about this library, but that’s the gist. Oh, and it’s called BuenaVista.

Let me know if you’d interested in collaborating :wink:

fceruti

fceruti

Yeah, I like it too. I even bought buenavista.dev.

Sure, there are many design decisions that shape this kind of tool. It would be awesome to pool resources, but I totally get if the kind of decision I made don’t make sense to you. Having said that, I really just want to have a library that makes is a contribution to the community, and in my wildest dreams, a reason why people choose Elixir/Phoenix over other language/framework combo.

I’m not particularly invested in the implementation.

cmo

cmo

CVA does something similar.

Where Next?

Popular in Questions Top

Kurisu
For example for a current url like http://localhost:4000/cosmetic/products?_utf8=✓&amp;query=perfume&amp;page=2, I would like to get: ...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
vac
Hi, I’m quite new in Elixir and I’m trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and I...
New
vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New
beno
I will often find my self writing things similar to: case some_value do nil -&gt; something() "" -&gt; something() _ -&gt; somethi...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New

Other popular topics Top

albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 52341 488
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
klo
Got a question about when to concat vs. prepending items to list then reversing to achieve appending. So i know lists boil down to [1 | ...
New
sergio
Kind of like when jquery came out, it was super necessary. Existing drag and drop libraries have a bunch of baggage to support old browse...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New

We're in Beta

About us Mission Statement