Common Elements

Layout

Our guiding principle is that components are responsible for their own internal layout, and should therefore avoid specifying external layout characteristics (such as for example margins on the component itself). Components can and should arrange the elements inside them (using for example margins or gaps), but should optimally not prescribe or interfere with layouts described on higher levels.

Pages

The default page layout requires a <body> element, which should contain a <main> element as its direct descendant. Additionally, the <header>, <aside> and <footer> optional elements are also supported (in any combination). The rendering typically follows the holy grail layout, with the <aside> element placed to the right according to the golden ratio.

Buttons

Text Buttons

Example

<div class="button-group">
  <!-- Links -->
  <div class="button-group horizontal">
    <a class="button">{{ _('Button') }}</a>
    <a class="button disabled">{{ _('Disabled') }}</a>
    <a class="button active">{{ _('Active') }}</a>
    <a class="button neutral">{{ _('Neutral') }}</a>
    <a class="button danger">{{ _('Danger') }}</a>
  </div>

  <!-- Buttons -->
  <div class="button-group horizontal">
    <button type="button">{{ _('Button') }}</button>
    <button type="button" class="disabled">{{ _('Disabled') }}</button>
    <button type="button" class="active">{{ _('Active') }}</button>
    <button type="button" class="neutral">{{ _('Neutral') }}</button>
    <button type="button" class="danger">{{ _('Danger') }}</button>
  </div>
</div>
<div class="button-group">
  <!-- Links -->
  <div class="button-group horizontal">
    <a class="button">Button</a>
    <a class="button disabled">Disabled</a>
    <a class="button active">Active</a>
    <a class="button neutral">Neutral</a>
    <a class="button danger">Danger</a>
  </div>

  <!-- Buttons -->
  <div class="button-group horizontal">
    <button type="button">Button</button>
    <button type="button" class="disabled">Disabled</button>
    <button type="button" class="active">Active</button>
    <button type="button" class="neutral">Neutral</button>
    <button type="button" class="danger">Danger</button>
  </div>
</div>

Icon Buttons

Example

<div class="button-group horizontal">
  <!-- Icon-only button -->
  <button type="button" class="icon">
    {{ include_static('django_logikal/icons/x.svg') }}
  </button>

  <!-- Icon-only toggle -->
  <button type="button"
          class="icon toggle"
          title="{{ _('Show password') }}"
          aria-label="{{ _('Show password') }}"
          data-active-title="{{ _('Hide password') }}"
          data-active-aria-label="{{ _('Hide password') }}">
    <span class="inactive" aria-hidden="false">
      {{ include_static('django_logikal/icons/eye-off.svg') }}
    </span>
    <span class="active" aria-hidden="true">
      {{ include_static('django_logikal/icons/eye.svg') }}
    </span>
  </button>

  <!-- Icon and text button -->
  <button type="button" class="icon text">
    {{ include_static('django_logikal/icons/x.svg') }}
    <span>{{ _('Close') }}</span>
  </button>
</div>
<div class="button-group horizontal">
  <!-- Icon-only button -->
  <button type="button" class="icon">
    <svg
      xmlns="http://www.w3.org/2000/svg"
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      stroke-width="2"
      stroke-linecap="round"
      stroke-linejoin="round"
      class="lucide lucide-x preview-icon">
      <path d="M18 6 6 18" />
      <path d="m6 6 12 12" />
    </svg>
  </button>

  <!-- Icon-only toggle -->
  <button
    type="button"
    class="icon toggle"
    title="Show password"
    aria-label="Show password"
    data-active-title="Hide password"
    data-active-aria-label="Hide password">
    <span class="inactive" aria-hidden="false">
      <svg
        xmlns="http://www.w3.org/2000/svg"
        width="24"
        height="24"
        viewBox="0 0 24 24"
        fill="none"
        stroke="currentColor"
        stroke-width="2"
        stroke-linecap="round"
        stroke-linejoin="round"
        class="lucide lucide-eye-off preview-icon">
        <path
          d="M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49" />
        <path d="M14.084 14.158a3 3 0 0 1-4.242-4.242" />
        <path
          d="M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143" />
        <path d="m2 2 20 20" />
      </svg>
    </span>
    <span class="active" aria-hidden="true">
      <svg
        xmlns="http://www.w3.org/2000/svg"
        width="24"
        height="24"
        viewBox="0 0 24 24"
        fill="none"
        stroke="currentColor"
        stroke-width="2"
        stroke-linecap="round"
        stroke-linejoin="round"
        class="lucide lucide-eye preview-icon">
        <path
          d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0" />
        <circle cx="12" cy="12" r="3" />
      </svg>
    </span>
  </button>

  <!-- Icon and text button -->
  <button type="button" class="icon text">
    <svg
      xmlns="http://www.w3.org/2000/svg"
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      stroke-width="2"
      stroke-linecap="round"
      stroke-linejoin="round"
      class="lucide lucide-x preview-icon">
      <path d="M18 6 6 18" />
      <path d="m6 6 12 12" />
    </svg>
    <span>Close</span>
  </button>
</div>

Dialogs

Popover

Example

<div class="example-page example-section">
  <div class="popover">Popover</div>
</div>
<div class="example-page example-section">
  <div class="popover">Popover</div>
</div>

Component Macros

template django_logikal/components/commons.html.j

Usage:

{% import 'django_logikal/components/commons.html.j' as commons %}
{% block component_head %}{{ component_head('commons') }}{% endblock %}

Components:

macro commons.language_switcher(current_language_code, action_url, csrf_input, id='language-switcher', text=None, available_languages=None, dropdown_button=True, dropdown_button_icon='django_logikal/icons/chevron-down.svg', compact_button=False, compact_button_icon='django_logikal/icons/globe.svg')

Render a language switcher.

Parameters:
  • current_language_code (str) – The current language code.

  • action_url (str) – The action URL to use.

  • csrf_input (str) – The CSRF input element to use.

  • id (str) – The ID to use for the menu element.

  • text (str) – The button text to use. Defaults to the current language name.

  • available_languages (list[tuple[str, str]]) – A list of available language code, translated language name pairs. Defaults to the configured languages.

  • dropdown_button (bool) – Whether to add a dropdown button.

  • dropdown_button_icon (str) – The path for the dropdown button icon to use.

  • compact_button (bool) – Whether to add a compact button.

  • compact_button_icon (str) – The icon to use for the compact button.

Note

The compact version is hidden by default. You can switch to showing the compact version and hiding the dropdown version using an approach similar to the following declarations:

@media (width <= 68.75rem) {
  nav .language-switcher {
    button.icon.dropdown { display: none; }
    button.icon.compact { display: block; }
    menu li:has(.button.disabled) { display: block; }
  }
}

Example

<nav>
  {{ commons.language_switcher(
    current_language_code=language(),
    action_url=url('set_language'),
    csrf_input=csrf_input
  ) }}
</nav>
<nav>
  <menu class="language-switcher" id="language-switcher" role="menu">
    <li role="none">
      <button
        type="button"
        id="language-switcher-toggle-dropdown"
        class="icon dropdown"
        role="menuitem"
        title="Change language"
        aria-label="Change language"
        aria-haspopup="menu"
        aria-expanded="false"
        aria-controls="language-switcher-form">
        English (US)
        <svg
          xmlns="http://www.w3.org/2000/svg"
          width="24"
          height="24"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
          stroke-linecap="round"
          stroke-linejoin="round"
          class="lucide lucide-chevron-down preview-icon">
          <path d="m6 9 6 6 6-6" />
        </svg>
      </button>
      <form id="language-switcher-form" action="set_language/" method="post">
        <menu role="menu" class="popover">
          <li role="none">
            <button name="language" value="en-gb" type="submit" role="menuitem">
              English (GB)
            </button>
          </li>
          <li role="none">
            <a class="button disabled">English (US)</a>
          </li>
        </menu>
      </form>
    </li>
  </menu>
</nav>

macro commons.desktop_menu(items, request, id='menu-desktop', dropdown_icon='django_logikal/icons/chevron-down.svg')

Render a desktop menu bar.

Parameters:
  • items (list) – A list of MenuItem or MenuCustomItem instances.

  • request (HttpRequest | None) – The HTTP request to use.

  • id (str) – The menu ID to use.

  • dropdown_icon (str) – The path for the dropdown icon to use.

Example

<nav>
  {{ commons.desktop_menu([
    menu_item(title='Home', view_name='main:home'),
    menu_item(title='Info', submenu=[
      menu_item(title='Home', id='submenu-home', view_name='main:home'),
      menu_item(title='About', view_name='main:about'),
      menu_item(title='Components', submenu=[
        menu_item(title='Home', id='subsubmenu-home', view_name='main:home'),
        menu_item(title='Buttons', view_name='main:buttons'),
      ]),
    ]),
    menu_item(title='Blog', submenu=[
      menu_item(title='Newest', view_name='main:blog', view_kwargs={'order': 'desc'}),
      menu_item(title='Oldest', view_name='main:blog', view_kwargs={'order': 'asc'}),
    ]),
  ], request=request) }}
</nav>
<nav>
  <menu role="menubar" id="menu-desktop" class="desktop">
    <li role="none">
      <a
        role="menuitem"
        id="menu-desktop-home"
        class="button disabled"
        aria-current="page">
        Home
      </a>
    </li>
    <li role="none">
      <button
        type="button"
        class="icon dropdown"
        role="menuitem"
        id="menu-desktop-info"
        aria-haspopup="menu"
        aria-expanded="false">
        Info
        <svg
          xmlns="http://www.w3.org/2000/svg"
          width="24"
          height="24"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
          stroke-linecap="round"
          stroke-linejoin="round"
          class="lucide lucide-chevron-down preview-icon">
          <path d="m6 9 6 6 6-6" />
        </svg>
      </button>
      <menu role="menu" class="popover">
        <li role="none">
          <a
            role="menuitem"
            id="menu-desktop-submenu-home"
            class="button disabled"
            aria-current="page">
            Home
          </a>
        </li>
        <li role="none">
          <a
            role="menuitem"
            id="menu-desktop-about"
            class="button"
            href="main/about//">
            About
          </a>
        </li>
        <li role="none">
          <button
            type="button"
            class="icon dropdown horizontal"
            role="menuitem"
            id="menu-desktop-components"
            aria-haspopup="menu"
            aria-expanded="false">
            Components
            <svg
              xmlns="http://www.w3.org/2000/svg"
              width="24"
              height="24"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2"
              stroke-linecap="round"
              stroke-linejoin="round"
              class="lucide lucide-chevron-down preview-icon">
              <path d="m6 9 6 6 6-6" />
            </svg>
          </button>
          <menu role="menu" class="popover">
            <li role="none">
              <a
                role="menuitem"
                id="menu-desktop-subsubmenu-home"
                class="button disabled"
                aria-current="page">
                Home
              </a>
            </li>
            <li role="none">
              <a
                role="menuitem"
                id="menu-desktop-buttons"
                class="button"
                href="main/buttons//">
                Buttons
              </a>
            </li>
          </menu>
        </li>
      </menu>
    </li>
    <li role="none">
      <button
        type="button"
        class="icon dropdown"
        role="menuitem"
        id="menu-desktop-blog"
        aria-haspopup="menu"
        aria-expanded="false">
        Blog
        <svg
          xmlns="http://www.w3.org/2000/svg"
          width="24"
          height="24"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
          stroke-linecap="round"
          stroke-linejoin="round"
          class="lucide lucide-chevron-down preview-icon">
          <path d="m6 9 6 6 6-6" />
        </svg>
      </button>
      <menu role="menu" class="popover">
        <li role="none">
          <a
            role="menuitem"
            id="menu-desktop-newest"
            class="button"
            href="main/blog/order/desc/">
            Newest
          </a>
        </li>
        <li role="none">
          <a
            role="menuitem"
            id="menu-desktop-oldest"
            class="button"
            href="main/blog/order/asc/">
            Oldest
          </a>
        </li>
      </menu>
    </li>
  </menu>
</nav>

macro commons.mobile_menu(items, request, id='menu-mobile', dropdown_icon='django_logikal/icons/chevron-down.svg', menu_icon='django_logikal/icons/menu.svg', menu_close_icon='django_logikal/icons/x.svg')

Render a mobile menu.

Parameters:
  • items (list) – A list of MenuItem or MenuCustomItem instances.

  • request (HttpRequest | None) – The HTTP request to use.

  • id (str) – The menu ID to use.

  • dropdown_icon (str) – The path for the dropdown icon to use.

  • menu_icon (str) – The path for the menu icon to use.

  • menu_close_icon (str) – The path for the menu close icon to use.

Example

<nav>
  {% set custom_item %}
    <a class="button" href="{{ url('main:custom') }}">Custom Item</a>
  {% endset %}

  {{ commons.mobile_menu([
    menu_item(title='Home', view_name='main:home'),
    menu_item(title='Info', submenu=[
      menu_item(title='Home', id='submenu-home', view_name='main:home'),
      menu_item(title='About', view_name='main:about'),
      menu_item(title='Components', submenu=[
        menu_item(title='Home', id='subsubmenu-home', view_name='main:home'),
        menu_item(title='Buttons', view_name='main:buttons'),
      ]),
    ]),
    menu_item(title='Blog', submenu=[
      menu_item(title='Newest', view_name='main:blog', view_kwargs={'order': 'desc'}),
      menu_item(title='Oldest', view_name='main:blog', view_kwargs={'order': 'asc'}),
    ]),
    menu_custom_item(content=custom_item),
    menu_item(title='Settings', view_name='main:settings', classes=['neutral']),
    menu_item(title='Log in', view_name='main:login', classes=['active']),
  ], request=request) }}
</nav>
<nav>
  <div class="menu-mobile-container">
    <button
      class="icon toggle"
      id="menu-mobile-toggle"
      type="button"
      title="Show menu"
      aria-label="Show menu"
      data-active-title="Hide menu"
      data-active-aria-label="Hide menu"
      aria-haspopup="true"
      aria-controls="menu-mobile"
      aria-expanded="false">
      <span class="inactive" aria-hidden="false"
        ><svg
          xmlns="http://www.w3.org/2000/svg"
          width="24"
          height="24"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
          stroke-linecap="round"
          stroke-linejoin="round"
          class="lucide lucide-menu preview-icon">
          <path d="M4 5h16" />
          <path d="M4 12h16" />
          <path d="M4 19h16" /></svg
      ></span>
      <span class="active" aria-hidden="true"
        ><svg
          xmlns="http://www.w3.org/2000/svg"
          width="24"
          height="24"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
          stroke-linecap="round"
          stroke-linejoin="round"
          class="lucide lucide-x preview-icon">
          <path d="M18 6 6 18" />
          <path d="m6 6 12 12" /></svg
      ></span>
    </button>
    <menu id="menu-mobile" role="menubar" class="mobile popover">
      <li role="none">
        <a
          role="menuitem"
          id="menu-mobile-home"
          class="button disabled"
          aria-current="page">
          Home
        </a>
      </li>
      <li role="none">
        <button
          type="button"
          class="icon dropdown"
          role="menuitem"
          id="menu-mobile-info"
          aria-haspopup="menu"
          aria-expanded="false">
          Info
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="24"
            height="24"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            stroke-width="2"
            stroke-linecap="round"
            stroke-linejoin="round"
            class="lucide lucide-chevron-down preview-icon">
            <path d="m6 9 6 6 6-6" />
          </svg>
        </button>
        <menu role="menu" class="popover">
          <li role="none">
            <a
              role="menuitem"
              id="menu-mobile-submenu-home"
              class="button disabled"
              aria-current="page">
              Home
            </a>
          </li>
          <li role="none">
            <a
              role="menuitem"
              id="menu-mobile-about"
              class="button"
              href="main/about//">
              About
            </a>
          </li>
          <li role="none">
            <button
              type="button"
              class="icon dropdown"
              role="menuitem"
              id="menu-mobile-components"
              aria-haspopup="menu"
              aria-expanded="false">
              Components
              <svg
                xmlns="http://www.w3.org/2000/svg"
                width="24"
                height="24"
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                stroke-width="2"
                stroke-linecap="round"
                stroke-linejoin="round"
                class="lucide lucide-chevron-down preview-icon">
                <path d="m6 9 6 6 6-6" />
              </svg>
            </button>
            <menu role="menu" class="popover">
              <li role="none">
                <a
                  role="menuitem"
                  id="menu-mobile-subsubmenu-home"
                  class="button disabled"
                  aria-current="page">
                  Home
                </a>
              </li>
              <li role="none">
                <a
                  role="menuitem"
                  id="menu-mobile-buttons"
                  class="button"
                  href="main/buttons//">
                  Buttons
                </a>
              </li>
            </menu>
          </li>
        </menu>
      </li>
      <li role="none">
        <button
          type="button"
          class="icon dropdown"
          role="menuitem"
          id="menu-mobile-blog"
          aria-haspopup="menu"
          aria-expanded="false">
          Blog
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="24"
            height="24"
            viewBox="0 0 24 24"
            fill="none"
            stroke="currentColor"
            stroke-width="2"
            stroke-linecap="round"
            stroke-linejoin="round"
            class="lucide lucide-chevron-down preview-icon">
            <path d="m6 9 6 6 6-6" />
          </svg>
        </button>
        <menu role="menu" class="popover">
          <li role="none">
            <a
              role="menuitem"
              id="menu-mobile-newest"
              class="button"
              href="main/blog/order/desc/">
              Newest
            </a>
          </li>
          <li role="none">
            <a
              role="menuitem"
              id="menu-mobile-oldest"
              class="button"
              href="main/blog/order/asc/">
              Oldest
            </a>
          </li>
        </menu>
      </li>
      <li role="none">
        <a class="button" href="main/custom/">Custom Item</a>
      </li>
      <li role="none">
        <a
          role="menuitem"
          id="menu-mobile-settings"
          class="button neutral"
          href="main/settings//">
          Settings
        </a>
      </li>
      <li role="none">
        <a
          role="menuitem"
          id="menu-mobile-log-in"
          class="button active"
          href="main/login//">
          Log in
        </a>
      </li>
    </menu>
  </div>
</nav>

macro commons.messages(messages, id='messages')

Show messages as a popover notification.

Parameters:
  • messages (list[str] | None) – A list of messages to show.

  • id (str) – The element ID to use.

Example

<div class="example-page">
  {{ commons.messages(messages|default(none)) }}
</div>
<div class="example-page">
  <div id="messages" class="popover notification">
    <ul>
      <li>Notification message.</li>
    </ul>
    <button id="messages-dismiss" type="button" class="active">Dismiss</button>
  </div>
</div>

class django_logikal.components.commons.MenuItem(title: str, view_name: str | None = None, view_kwargs: dict[str, ~typing.Any] = <factory>, submenu: list[~django_logikal.components.commons.MenuItem | ~django_logikal.components.commons.MenuCustomItem] = <factory>, id: str | None = None, classes: list[str] = <factory>)

An item in a menu rendered as a menu button.

class django_logikal.components.commons.MenuCustomItem(content: str)

An item in a menu rendered as custom content.