Skip to main content

UI Assets Overrides

All the assets served to the UI of Hanami can be overridden. For this, one would need to add to their customer.yml file something like this:

local:
assets:
folder: /opt/hanami/ext/override

Like this, files in the /opt/hanami/ext/override folder will be put in the folder serving UI assets. This could override any file in there even index.html, but we recommend to not blindly override any random file. Instead, we procure here a list of usual overrides that serves dedicates purposes.

i18n files

By default, Hanami loads 2 files when rendering the application in a given language. For example when the application is in English, it loads assets/i18n/en.json and assets/i18n/en.customer.json. Those files are holding most of the labels used in the application, structured in deep string maps that serves as keywords to map to labels. For example part of en.json looks like this:

{
"global": {
"cancel": "Cancel",
"close": "Close",
"collection": "Collection",
"copy_uri": "Copy URI",
"created": "Created",
"delete": "Delete"
},
"some_other_key": {
"that_can_go": {
"super_deep": "foo",
"something": "else"
}
}
}

Which means that, when the interface is trying to find a label for the key some_other_key.that_can_go.super_deep, it will print the label foo. And for some_other_key.that_can_go.something, it will print else.

Now considering en.customer.json. By default, it is served as an empty json {} and so will not have any impact. But let's say that we configured the override by using the opt/hanami/ext folder, then one could add there /opt/hanami/ext/assets/i18n/en.customer.json that would look like this:

{
"some_other_key": {
"that_can_go": {
"super_deep": "bar"
}
}
}

With this, when the interface will try and display a label for some_other_key.that_can_go.super_deep, it will print the label bar. And since we are doing a deep merge of the 2 maps, some_other_key.that_can_go.something will still be printed as else.

Customer specific keywords

If you need to add new keywords to the translation map, we advise to put them under a dedicated customer to be sure to not accidentally override keywords used elsewhere in the application. Then a typical en.customer.json file would look like this:

{
"customer": {
"my_custom_key": "Hello World"
}
}

This is especially useful when configuring other Hanami features that make use of translation labels, like workflow actions or footer links labels (see below).

UI Config file

An other file that is loaded on start is assets/config.json, and by default it is an empty json {}. Put properties can be added to it to configure some features.

When overriding assets/config.json, one could set some properties as below:

{
"header": {
"app_title": "customer.header.app_title",
"app_subtitle": "customer.header.app_subtitle",
"navMode": "bar",
"logo": {
"src": "assets/img/logo.png",
"alt": "customer.header.logo_alt",
"width": 81,
"height": 45
}
}
}

The header can have a few properties set:

  • app_title: The title od the application, if you desire to override the default "Hanami". Please note that we are using keywords here, and so related translations should be provided in the i18n files as explained above.
  • app_subtitle adding a second line to the title, styled differently
  • navMode: specify the way the navigation bar behaves in the header. There are multiple possible values:
    • bar: display the navigation items directly in the header
    • burger: display the navigation items in a "burger menu" that can be opened from the left of the application logo
    • hybrid: An in-between solution where bar is used on the home page, and burger on most others. This configuration is not considered a11y friendly at the moment, so we advise against it for now.
  • logo: specify which logo to use instead of the default one. All properties here are needed:
    • src: url to the image to be loaded
    • alt: descriptive text to add to the image, especially useful for users making use of screen readers.
    • width: width the image should take, in pixels
    • height: height the image should take, in pixels

Similarly to the header, the footer can be configured in a similar fashion, having also links and a logo parameters:

{
"footer": {
"logo": {
"src": "assets/img/footer-logo.png",
"alt": "customer.footer.logo_alt",
"width": 236,
"height": 35
},
"links": [
{
"labelKey": "customer.footer.example",
"url": "https://example.com"
},
{
"labelKey": "customer.footer.send_feedback",
"url": "mailto:feedback@example.com"
}
]
}
}

Item Form

Item forms can be configured too, like so

{
"itemForm": {
"localNavigation": {
"goBackButton": {
"enabled": false
},
"goUpButton": {
"enabled": false
}
}
}
}

The goBackButton and goUpButton are configuring the behavior of the buttons that appear near the title of the node being currently edited. By default, both those buttons are enabled, so if you want to disable them, they need to be explicitly set to false like in the example above.

Toasts

Toasts can be customized a bit too. Those are the popups that come up, for example, when a save was successful or if an error occurred. Such config would look lik this:

{
"toasts": {
"_default": {
"duration": 10000
},
"success": {
"duration": 5000
},
"error": {
"duration": 0
},
"warning": {
"duration": 0
},
"info": {
"duration": 0
}
}
}

The durations are given in milliseconds, so with this given config, a success message would disappear after 5 seconds, and all other message type would only disappear when the user manually discard the toast.

There is also the special case of the _default key. This applies configuration for toast type that do not have one. So fore example this config

{
"toasts": {
"_default": {
"duration": 10000
},
"success": {
"duration": 5000
},
"error": {
"duration": 0
}
}
}

Successes will disappear after 5 seconds, errors will need to be manually discarded, and warnings and info will disappear after 10 seconds. The default toast config is equivalent to

{
"toasts": {
"_default": {
"duration": 10000
}
}
}

It is to be noted that, for accessibility concerns, you might want to augment this duration, or even set it to 0 for all toast types.

Styles

Finally, one can configure the injection of custom css files, like so:

{
"styles": {
"files": ["assets/styles/customer.css"]
}
}

You can then put any styling that you want there and customize Hanami to your heart's content. For now, we advise to limit this customization only through overriding css variables, for example like so:

:root {
--font-family-main: "Helvetica", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--color-primary: #0d6efd;
--color-primary-dark: #0d5286;
--color-primary-intense: #0d5286;
--color-secondary: #6b31a5;
--color-secondary-intense: #360c50;
}

Here is the list of variables that we advise to first override if the need arises:

:root {
/* Primary colors, used in most places */
--color-primary: var(--color-blue-intense);
--color-primary-dark: var(--color-blue-dark);
--color-primary-light: var(--color-blue-light);
--color-primary-bg: var(--color-blue-bg);
--color-primary-hover: var(--color-blue-hover);
--color-primary-disabled: var(--color-blue-disabled);
/* border colors, depending on the desired intensity */
--color-border-soft: var(--color-gray-10);
--color-border-hard: var(--color-gray-50);
/* A secondary palette of colors, used as an accent color to draw attention to some parts of the page */
--color-secondary: var(--color-violet);
--color-secondary-bg: var(--color-violet-bg);
--color-secondary-intense: var(--color-violet-intense);
--color-secondary-light: var(--color-violet-light);
--color-link: var(--color-primary);
/* color of fonts used, high is usually for titles, medium is for more discreet texts. Light is really rarely used */
--color-font-high: var(--color-dark);
--color-font-medium: var(--color-gray-70);
--color-font-light: #686868;
/* Color used for error state, like inlined errors in forms, or error prompts */
--color-error: var(--color-red);
/* Color used for warning state, mostly ever used in rare warning prompts prompts */
--color-warning: var(--color-orange-intense);
/* Colors used for success state, mostly for success toasts after an async operation succeeds (like a creation/save) */
--color-success: var(--color-green);
--color-success-bg: var(--color-green-bg);
/* Fonts to be used in the application */
--font-family-main: "Inter", sans-serif;
--font-family-secondary: "Inter", sans-serif;
}

Other styling could be added of course, but then we advise to use components tag names to somewhat limit the scope of the customization. For example one could add something like this:

hanami-page-title .hanami-page-title-content {
color: var(--color-primary) !important;
font-weight: 400 !important;
font-family: var(--font-family-secondary) !important;
}

It is to be noted that styling outside of css variables could break on new Hanami releases, so custom styling should ideally be kept to a minimal set of change.