Themeing sites in Lucky
Tags: crystal, lucky
Published: 2018-07-12 18:49:37

---
title: Themeing sites in Lucky
published: true
description: How to setup themes for Lucky
tags: crystal, lucky
---

*NOTE: this is based on my first round of getting a multi-tenant app to use themes. It's not the cleanest way, but it works for now*

Assuming you're using [Lucky](https://luckyframework.org/) and you send multiple domains to that same app, you may need to style each site slightly different. To do this, you'd want to theme each site. In these examples, I have a `Site` model that has a `theme` column.

The first thing we will do is create a mixin for our actions. In `src/actions/mixins/` create a new file `themable.cr`.

```ruby
module Themable
  macro included
    expose current_theme
  end

  enum Themes
    Default
    Dark
  end
 
  # if it returns nil or a non-listed theme
  # just return the default 
  def current_theme : String
    # `current_site` is exposed from a different bit
    t = current_site.theme
    Themes.from_value?(t) ? Themes.from_value?(t).to_s : "Default"
  end
end
```

Now that we have access to a `current_theme` method, we just need to include this mixin. In your `src/actions/browser_action.cr`

```ruby
abstract class BrowserAction < Lucky::Action
  include Themable
  #...

  macro theme_page
    if current_theme == "Default"
      {{ @type }}Page
    else
      case Themes.parse(current_theme)
      when .dark?
        Dark::{{ @type }}Page
      end
    end
  end
end
```

In this BrowserAction, I added a macro to help determine which page we are going to render. This will make a bit more sense in the actions.

```ruby
class Dashboards::Index < BrowserAction
  get "/" do
    render(theme_page)
  end
end
```

In this root action here, we now render either `Dashboards::IndexPage` or `Dark::Dashboards::IndexPage`. This is depending on which `current_site` has been loaded. As long as you're keeping up with traditional naming conventions, then this just all works.

Now it's time to setup the views portion. In our `src/pages/main_layout.cr` file, we'll need to update some stuff.

```ruby
abstract class MainLayout
  include Lucky::HTMLPage

  needs current_theme : String

  abstract def content

  def render
    html_doctype

    html lang: "en" do
      head do
        #... other stuff
        css_link(dynamic_asset("#{@current_theme.downcase}.css"))
      end
      body do
        content
      end
    end
  end
end
```

For this MainLayout, we are calling either `default.css` or `dark.css`. There's a ton of different ways you could handle this. Maybe you have a `base.css`, and then just inherit and override, or maybe you include a secondary css file? What ever you decide to do with that, just make sure your webpack mix file is updated with all these theme styles.

Another issue you may run in to here is, maybe you have completely different header or footers depending on the theme. You may need to do some additional logic here like:

```ruby
body do
  if @current_theme == "Dark"
    render_dark_header
  end
  content
end
```

This of course could be abstracted out to a module in `src/components/header_component.cr`

```ruby
module HeaderComponent
  private def render_header(theme)
    case Themable::Themes.parse(theme)
    when .dark?
      _dark_header
    end
  end

  private def _dark_header
    div(class: "dark") do
      h1("Dark Header")
    end
  end
end

abstract class MainLayout
  #...
  include HeaderComponent


  def render
    #...
    body do
      render_header(@current_theme)
      content
    end
  end
end
```

Lastly, we just need the main view part! For this, we're going to use a special directory structure. We currently have `src/pages/dashboards/index_page.cr`, and we will just add `src/pages/dashboards/dark/index_page.cr`. This sort of breaks the hierarchy standard, but for me personally it makes more sense. (for now at least, I'm sure I'll hate it in 3 months lol)

```ruby
class Dashboards::IndexPage < MainLayout
  def content
    #... home page content here
  end
end

class Dark::Dashboards::IndexPage < ::Dashboards::IndexPage
  def content
    #... dark theme home page content.

    # AND... Bonus! 
    previous_def
    # add additional stuff that's on dark theme and not default
  end
end
```

Really, that's basically it. Everything else is just going to be your personal preference, or additional things you may need.

If you're reading this, and you can think of some cleaner ways, please comment below!