PrimerLive.Theme (PrimerLive v0.7.0)

Primer CSS contains styles for light/dark color modes and themes, with support for color blindness.

PrimerLive provides components and functions to work with themes:

Persistency

There is no easy way to save persistent session data in LiveView because LiveView's state is stored in a process that ends when the page is left.

Session for theme state has been removed

Using the session to store the theme state (for example using a AJAX call roundtrip) does not work as expected: when navigating to another LiveView page, the updated session data is not refetched and only becomes available after a page refresh. This means that the previous offered solution with PrimerLive.ThemeSessionController is no longer recommended and in fact removed.

Alternatives: database or cache

If you already have a database set up for storing data by session ID, it's a small step to integrate the theme state with it.

On the other hand, for a lightweight solution you may find caching simpler to start with. For example Cachex (which I've selected for primer-live.org).

Handling user selection

Assuming you are providing a theme menu on the website, the option the user selects must be stored in persistent storage.

Here we're setting event callback "store_theme" in the theme menu:

# Some app component

<.action_menu>
  <:toggle class="btn btn-invisible">
    <.octicon name="sun-16" />
  </:toggle>
  <.theme_menu_options
    theme_state={@theme_state}
    update_theme_event="store_theme"
  />
</.action_menu>

The callback is invoked whenever a theme menu option is clicked. See attr update_theme_event for documentation of the arguments.

# App LiveView

def handle_event(
  "store_theme",
  %{"data" => data, "key" => key, "value" => _},
  socket
) do
  # Persist new theme state ...

  {:noreply, socket}
end

Summary

Functions

Configures menu options from supplied params

Default label for a theme menu.

Default options for a theme menu.

Initial theme state.

Creates HTML (data) attributes from the supplied theme state to set a theme on a component or element directly. This is useful to "theme" specific page parts regardless of the user selected theme, for example a dark page header.

Compares the supplied state with the supplied default state.

Default reset link identifier for the handle_event update callback.

Default event name for the handle_event update callback.

Functions

Link to this function

create_menu_items(theme, menu_options, menu_labels)

Configures menu options from supplied params:

  • theme: the current theme state (used to define the selected menu items)
  • menu_options: which menu options will be displayed
  • menu_labels: overrides of default text labels

Returns a list with 3 menu elements:

  • color_mode
  • dark_theme
  • light_theme

Each list item is a map that contains display attributes:

  • group label
  • option labels
  • the selected item

Tests

iex> PrimerLive.Theme.create_menu_items(
...> %{
...>   color_mode: "light",
...>   light_theme: "light_high_contrast",
...>   dark_theme: "dark_high_contrast"
...> },
...> PrimerLive.Theme.default_menu_options(),
...> PrimerLive.Theme.default_menu_labels()
...> )
[{:color_mode, %{labeled_options: [{"light", "Light"}, {"dark", "Dark"}, {"auto", "System"}], options: ["light", "dark", "auto"], selected: "light", title: "Theme"}},{:light_theme, %{options: ["light", "light_high_contrast", "light_colorblind", "light_tritanopia"], selected: "light_high_contrast", title: "Light tone", labeled_options: [{"light", "Light"}, {"light_high_contrast", "Light high contrast"}, {"light_colorblind", "Light colorblind"}, {"light_tritanopia", "Light Tritanopia"}]}},{ :dark_theme, %{ labeled_options: [{"dark", "Dark"}, {"dark_dimmed", "Dark dimmed"}, {"dark_high_contrast", "Dark high contrast"}, {"dark_colorblind", "Dark colorblind"}, {"dark_tritanopia", "Dark Tritanopia"}], options: ["dark", "dark_dimmed", "dark_high_contrast", "dark_colorblind", "dark_tritanopia"], selected: "dark_high_contrast", title: "Dark tone" }}]

iex> PrimerLive.Theme.create_menu_items(
...> %{
...>   color_mode: "light",
...>   light_theme: "light_high_contrast",
...>   dark_theme: "dark_high_contrast"
...> },
...> %{
...>   color_mode: ~w(light dark)
...> },
...> %{
...>   color_mode: %{
...>     light: "Light theme"
...>   },
...>   reset: "Reset"
...> })
[color_mode: %{labeled_options: [{"light", "Light theme"}, {"dark", "Dark"}], options: ["light", "dark"], selected: "light", title: "Theme"}]
Link to this function

default_menu_labels()

Default label for a theme menu.

Link to this function

default_menu_options()

Default options for a theme menu.

Link to this function

default_theme_state()

Initial theme state.

Link to this function

html_attributes(theme_state)

See html_attributes/2.

Link to this function

html_attributes(theme_state, default_theme_state)

Creates HTML (data) attributes from the supplied theme state to set a theme on a component or element directly. This is useful to "theme" specific page parts regardless of the user selected theme, for example a dark page header.

<.button
  {PrimerLive.Theme.html_attributes([color_mode: "dark", dark_theme: "dark_high_contrast"])}
>Button</.button>

<.octicon name="sun-24"
  {PrimerLive.Theme.html_attributes(%{color_mode: "dark", dark_theme: "dark_dimmed"})}
/>

Tests

iex> PrimerLive.Theme.html_attributes(
...> %{
...>   color_mode: "light",
...>   light_theme: "light_high_contrast",
...>   dark_theme: "dark_high_contrast"
...> }
...> )
[{:"data-color-mode", "light"}, {:"data-dark-theme", "dark_high_contrast"}, {:"data-light-theme", "light_high_contrast"}]

iex> PrimerLive.Theme.html_attributes(
...> %{
...> },
...> %{
...>   color_mode: "auto",
...>   light_theme: "light",
...>   dark_theme: "dark"
...> }
...> )
[{:"data-color-mode", "auto"}, {:"data-dark-theme", "dark"}, {:"data-light-theme", "light"}]

iex> PrimerLive.Theme.html_attributes(
...> %{
...>   light_theme: "light_high_contrast",
...> },
...> %{
...>   color_mode: "auto",
...>   light_theme: "light",
...>   dark_theme: "dark"
...> }
...> )
[{:"data-color-mode", "auto"}, {:"data-dark-theme", "dark"}, {:"data-light-theme", "light_high_contrast"}]

iex> PrimerLive.Theme.html_attributes(
...> %{
...> },
...> %{
...>   color_mode: "auto",
...> }
...> )
["data-color-mode": "auto"]
Link to this function

is_default_theme(theme, default_theme_state)

Compares the supplied state with the supplied default state.

Tests

iex> PrimerLive.Theme.is_default_theme(
...> %{
...>   color_mode: "auto",
...>   light_theme: "light",
...>   dark_theme: "dark"
...> },
...> PrimerLive.Theme.default_theme_state()
...> )
true

iex> PrimerLive.Theme.is_default_theme(
...> %{
...>   color_mode: "light",
...>   light_theme: "light_high_contrast",
...>   dark_theme: "dark_high_contrast"
...> },
...> PrimerLive.Theme.default_theme_state()
...> )
false

Default reset link identifier for the handle_event update callback.

Link to this function

update_theme_event_key()

Default event name for the handle_event update callback.

This value can be overridden in theme_menu_options with update_theme_event:

<.theme_menu_options
  theme_state={@theme_state}
  update_theme_event="store_theme"
/>