Why do root and app layouts differ slightly after removing Tailwind CSS and adding Simple CSS?

This one has left me rather confused.

I followed How to remove tailwind from Phoenix completely ? (I only want vanilla CSS) - #3 by abar to remove Tailwind CSS from a recent (latest version) Phoenix app I have started building.

I then added Simple CSS via the CDN option to the root.html.heex layout. Here’s the relevant part:

    <.live_title suffix="">
      <%= assigns[:page_title] || "Our Next Book" %>
    </.live_title>
    <link phx-track-static rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css" />
    <link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
    <script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
    </script>

When I visit the root of the app, which is handled by a normal HTML page (not a LiveView), the styling looks fine for the header and footer. But then, when I visit any LiveView most of the styling looks fine, except the header and footer:

Normal root page:

A LiveView:

Notice how the header and footer is missing some of the styling. Other than that, the rest of the page using the correct styling.

I haven’t changed anything else in the default configuration as far as I can tell.

What might be the issue?

Have you compared the generated HTML in the browser for both and used the class inspector to see which classes are being applied?

1 Like

Thank you for the suggestion @josevalim. Indeed, I hadn’t tried, but I have now. Here’s what I see (disclaimer, I am not an expert in CSS, nor do I understand how Phoenix LiveView is relying on the two default layouts (root and app)).

Here’s what I notice. The left page has this body > header style, that gives it those properties (light blue background, centered text, a horizontal divider etc.).

With the right page (LiveView), it seems some styles are not carried over or maybe they are overridden by something? Totally speculating here.

Maybe the way I have removed Tailwind CSS, has left something in place that messes this up? Again speculating.

When I compare the HTML, the only different (which probably makes sense) is that the LiveView has the body wrap in a div that has LiveView related attributes. Probably makes sense based on what I read in the docs.

At this point, I am not sure what to investigate.

What I am going to do, is create a totally fresh Phoenix project but this time use the -no-tailwind option. Then do a minimal example to replicate the situation. I will report back the result.

what’s in the simple.css file?
Where does the rule body > header come from?
how is it generated?

update: I see now, it comes from simple.css/simple.css at 3abdc127ca0c8e4b8fe6640ba7e625c5c4e628f2 · kevquirk/simple.css · GitHub

The issue probably is that simple.css expects a certain structure and that because of the injected div from liveview this structure breaks. You can read here about this container: Phoenix.Component — Phoenix LiveView v0.20.17

If you want to use simple.css I would just copy it in your project instead of using it via a cdn and then modifying it, so it works for you.

what’s in the simple.css file?

Here’s the whole file on GitHub.

Where does the rule body > header come from?

I think this is where it comes from: simple.css/simple.css at 3abdc127ca0c8e4b8fe6640ba7e625c5c4e628f2 · kevquirk/simple.css · GitHub

how is it generated?

I don’t think it’s generated. It’s just a direct reference using <link rel="stylesheet" href="https://cdn.simplecss.org/simple.css" />

I updated my previous post with some more information. I noticed your first post too late.

1 Like

Oh right! Indeed, that extra div changes the structure that Simple CSS expects. I have experimented a bit by just adding the exact same header and footer CSS to my app.css file, with the only difference that I have added that extra div:

body > div > header {
    background-color: var(--accent-bg);
    border-bottom: 1px solid var(--border);
    text-align: center;
    padding: 0 0.5rem 2rem 0.5rem;
    grid-column: 1 / -1;
}

body > div > header > *:only-child {
    margin-block-start: 2rem;
}

body > div > header h1 {
    max-width: 1200px;
    margin: 1rem auto;
}

body > div > header p {
    max-width: 40rem;
    margin: 1rem auto;
}

body > div > footer {
    margin-top: 4rem;
    padding: 2rem 1rem 1.5rem 1rem;
    color: var(--text-light);
    font-size: 0.9rem;
    text-align: center;
    border-top: 1px solid var(--border);
}

With this addition I am almost there:

I think there must be some other rule I haven’t brought from Simple CSS. I will keep on looking.

@josevalim I don’t think there’s anything at fault here. But I still wonder, would there be a way to not have to make all these adjustments to bring a plain simple CSS file and be sure it works consistently with both dead and live views?

Would you say Simple CSS’s choice to rely on body > header and body > footer, expecting that’s the definite structure across the web, is at fault here? Would it make sense proposing a different approach to the folks behind it?

Alright, here’s what I did, and now the header displays consistently across all views. I moved the header and footer tags from app to root. This removed the div between body and header.

I’d say it depends on their vision, if they want you to build something with their ‘framework’ and expect you to follow the exact structure, it’s not wrong of them.

Similarly it’s not wrong of the liveview team to inject an extra html element (which you can pick whatever you want)

So obviously these 2 are in conflict, but none of them are wrong.

Alright, here’s what I did, and now the header displays consistently across all views. I moved the header and footer tags from app to root. This removed the div between body and header.

but as soon as you have something rendered by liveview that expects the > css relation with body, it might break again.

Honestly, I’d just fork/copy simple.css and change what’s necessary. Easiest change would probably be to give your container an idea:

live_render socket, MyLiveView, container: {:div, id: "my-body"}

And in simple.css do a search replace for body > to: #my-body >

1 Like

Thank you! That makes sense. Indeed, one of their main values is to offer a classless framework. That’s why they approached it that way.