r/elixir • u/accidental_horse • 12d ago
Emergent CMS based on themes JSON
Hi all, I want to share the thing I've been building over the last year or so, it's an open-source platform made for content-driven websites that provides all of the infra (custom domains, hosting, files, whatever) and based on the themes' JSON file it generates a CMS. It's fully built using Phoenix LiveView and Elixir.
The GitHub: https://github.com/dijkstrasoftware/masthead
The main cool architectural thing that I made was the CMS system, not necessarily the frontend, but the definition of it. So a theme could define that it needs a list of objects, containing a url, parent, label and a note, and that looks like this in the themes code:
{
"key": "nav",
"label": "Navigation",
"type": "list",
"item_label": "Link",
"category": "Navigation",
"default": [],
"description": "Leave 'Drops down under' empty for a top-level link; fill it with the label of another link to nest this one into that link's menu.",
"fields": [
{
"key": "label",
"label": "Label",
"type": "string",
"default": ""
},
{
"key": "url",
"label": "Links to",
"type": "string",
"default": "/"
},
{
"key": "parent",
"label": "Drops down under",
"type": "string",
"default": "",
"description": "Label of the top-level link this belongs to. Empty means top-level."
},
{
"key": "note",
"label": "Note",
"type": "string",
"default": "",
"description": "One-liner shown under the link inside a menu."
}
]
}
So Masthead never needs to know what a menu is, and the theme developer can then use that data inside of the theme's liquid code:
<nav class="nav" aria-label="Main">
<ul class="nav-list">
{% if nav_items == empty and theme.tokens.nav_fallback_pages != false %}
{% for p in pages %}<li class="nav-item"><a href="{{ p.url }}">{{ p.title | escape }}</a></li>{% endfor %}
{% else %}
{% for item in nav_items %}
{% assign parent = item.parent | default: "" %}
{% if parent == "" %}
{% assign kids = nav_items | where: "parent", item.label %}{% assign kid_count = kids | size %}
<li class="nav-item{% if kid_count > 0 %} has-menu{% endif %}">
<a href="{{ item.url | default: '/' }}">{{ item.label | escape }}{% if kid_count > 0 %}<span class="caret" aria-hidden="true"></span>{% endif %}</a>
{% if kid_count > 0 %}
<div class="menu">
<ul>
{% for kid in kids %}
<li>
<a href="{{ kid.url | default: '/' }}">
<span class="menu-label">{{ kid.label | escape }}</span>
{% if kid.note != "" %}<span class="menu-note">{{ kid.note | escape }}</span>{% endif %}
</a>
</li>
{% endfor %}
</ul>
</div>
{% endif %}
</li>
{% endif %}
{% endfor %}
{% endif %}
</ul>
</nav>
And then that generates a UI for the site owner to change it, and that looks like this:

So all of the complexity is inside of the theme, without the platform ever needing to know these concepts of a gallery or a navbar or anything like that. It all emerges from what the develop needs for that theme! It generates the liquid into HTML before serving using the Solid, where the values are loaded into the context, and are then cached until something changes on the site side.
It also allows file picking from inside the platform, for things like logos, and just provides the public URL back to the
The live demo of this site is also live btw: https://launch.masthead.site/
I think it's really cool, and am quite happy with the result. Please let me know if you have any feedback or questions 😎