I’m in the process of converting my FileMaker vertical market solution into a BF app. One of the features I want to offer is the ability for my customers to change the theme of their app so that they colouring can better match their brand.
I’d initially been looking at doing this by creating TW CSS classes within app.env and then using Vue’s :class to reference these however this was a bit clunky and made ready the HTML code tricky. The code looked something like
:class="[1 ? app.env.themes[app.env.themeSelected].buttons.btnPrimary : '' ]"
Following the recent Friday Live session on Tailwind v3 I’ve developed a different approach that works really well.
Firstly, there are two things to add the the app’s DOM Header. The first extends TW to create theme50-900 colours based on one of the TW colour schemes. In my case Sky. This acts as my app default colour scheme.
<script>
tailwind.config = {
theme: {
extend: {
colors: {
theme50: '#f0f9ff',
theme100: '#e0f2fe',
theme200: '#bae6fd',
theme300: '#7dd3fc',
theme400: '#38bdf8',
theme500: '#0ea5e9',
theme600: '#0284c7',
theme700: '#0369a1',
theme800: '#075985',
theme900: '#0c4a6e'
},
}
}
}
</script>
The second is to create the custom classes which then uses the above for their colours. I’ve created quite a few of these for my app. One example is my primary button:
<style type="text/tailwindcss">
.btnPrimary {
@apply py-2 xl:py-1 px-5 text-lg rounded-full bg-theme600 border border-theme600 text-gray-100 hover:bg-theme800 hover:text-gray-100 transition-colors duration-500 transform cursor-pointer
}
</style>
Using this approach I’ve then been able to replace all of the :classes that I was using to reference my themes from bf.app with the custom classes so
:class="[1 ? app.env.themes[app.env.themeSelected].buttons.btnPrimary : '' ]"
can be massively simplified and becomes much easier to read
class="btnPrimary"
To change the entire colour scheme app wide the user can then select from the colour options from a dropdown in their app settings page. This sets app.theme
to their selection (which is cached in the Browser to remember the user’s selection if they refresh the page) and runs a Global NamedAction which updates the theme50-900 colours
//
//IMPORTANT
//
//If updated, this also needs to be copied into the onLogin script Insert Text script step
//
// Get the root element
var r = document.querySelector(':root');
//Get the selected theme
var theme = app.theme //This is cached in the browser when user changes it
debugger
//Update CSS based on selectedTheme
//SKY
if (theme == "sky") {
r.style.setProperty('--sidebarBackgroundColor', '#0C4A6E'); //900
r.style.setProperty('--sidebarBackgroundColorActive', '#075985'); //800
r.style.setProperty('--sidebarBackgroundColorHover', '#0284C7'); //600
r.style.setProperty('--sidebarTextColor', '#ffffff');
r.style.setProperty('--sidebarTextColor', '#dddddd');
r.style.setProperty('--sidebarActiveTab', '#7DD3FC'); //300
tailwind.config = {
theme: {
extend: {
colors: {
//Sky
theme50: '#f0f9ff',
theme100: '#e0f2fe',
theme200: '#bae6fd',
theme300: '#7dd3fc',
theme400: '#38bdf8',
theme500: '#0ea5e9',
theme600: '#0284c7',
theme700: '#0369a1',
theme800: '#075985',
theme900: '#0c4a6e'
},
}
}
}
//EMERALD
} else if (theme == "emerald") {
r.style.setProperty('--sidebarBackgroundColor', '#064e3b'); //900
r.style.setProperty('--sidebarBackgroundColorActive', '#065f46'); //800
r.style.setProperty('--sidebarBackgroundColorHover', '#059669'); //600
r.style.setProperty('--sidebarTextColor', '#ffffff');
r.style.setProperty('--sidebarTextColor', '#dddddd');
r.style.setProperty('--sidebarActiveTab', '#6ee7b7'); //300
tailwind.config = {
theme: {
extend: {
colors: {
theme50: '#ecfdf5',
theme100: '#d1fae5',
theme200: '#a7f3d0',
theme300: '#6ee7b7',
theme400: '#34d399',
theme500: '#10b981',
theme600: '#059669',
theme700: '#047857',
theme800: '#065f46',
theme900: '#064e3b'
},
}
}
}
//SKY IS THE DEFAULT
} else {
r.style.setProperty('--sidebarBackgroundColor', '#0C4A6E'); //900
r.style.setProperty('--sidebarBackgroundColorActive', '#075985'); //800
r.style.setProperty('--sidebarBackgroundColorHover', '#0284C7'); //600
r.style.setProperty('--sidebarTextColor', '#ffffff');
r.style.setProperty('--sidebarTextColor', '#dddddd');
r.style.setProperty('--sidebarActiveTab', '#7DD3FC'); //300
tailwind.config = {
theme: {
extend: {
colors: {
//Sky
theme50: '#f0f9ff',
theme100: '#e0f2fe',
theme200: '#bae6fd',
theme300: '#7dd3fc',
theme400: '#38bdf8',
theme500: '#0ea5e9',
theme600: '#0284c7',
theme700: '#0369a1',
theme800: '#075985',
theme900: '#0c4a6e'
},
}
}
}
}
To full customise the theme I also needed to theme the sideNavBar. (I don’t use the topNavBar but this could also be done if required).
I did this using CSS variables and above code r.style.setProperty('--sidebarBackgroundColor', '#0C4A6E')
to update the sidebar colours. These work with the following Site CSS:
root {
--sidebarBackgroundColor: #0C4A6E; /*sky-900*/
--sidebarBackgroundColorActive: #075985; /*sky-800*/
--sidebarBackgroundColorHover: #0284C7; /*sky-600*/
--sidebarTextColor: #ffffff;
--sidebarHeaderColor: #dddddd;
--sidebarActiveTab: #7DD3FC; /*sky-300*/
}
/* SIDE NAV */
.sidebar {
padding-top: 20px !important;
border-right: none;
}
.theme-blue-full .sidebar-left {
background-color: var(--sidebarBackgroundColor);
}
.sidebar-header {
color: var(--sidebarHeaderColor);
font-size: 12px;
}
.sidebar .nav-pills {
margin-left: 0 !important;
margin-right: 0 !important;
}
.sidebar .nav-pills>li>a,
.sidebar .nav-pills>li>a:visited {
padding: 9px 10px 9px 14px;
font-size: 0.9em;
color: var(--sidebarTextColor);
border-radius: 0;
font-weight: 400;
border-left: 6px solid var(--sidebarBackgroundColor);
}
.sidebar .nav-pills>li>a:hover {
color: var(--sidebarTextColor);
}
.theme-blue-full.sidebar-mini .sidebar-left .nav>li.active>a,
.theme-blue-full.sidebar-mini .sidebar-left .nav>li.active>a:focus,
.theme-blue-full.sidebar-mini .sidebar-left .nav>li.active>a:hover,
.theme-blue-full .sidebar-left .nav>li.active>a,
.theme-blue-full .sidebar-left .nav>li.active>a:focus,
.theme-blue-full .sidebar-left .nav>li.active>a:hover {
color: var(--sidebarTextColor);
background-color: var(--sidebarBackgroundColorActive);
border-radius: 0;
border-left: 6px solid var(--sidebarActiveTab);
padding: 9px 10px 9px 14px;
}
@media (min-width: 768px) {
.sidebar-mini .sidebar {
width: 50px;
margin-top: 0px;
-webkit-transition: all 0.1s ease-in-out;
-moz-transition: all 0.1s ease-in-out;
-o-transition: all 0.1s ease-in-out;
-ms-transition: all 0.1s ease-in-out;
transition: all 0.1s ease-in-out;
}
.sidebar-mini .sidebar .nav>li>a:hover {
color: var(--sidebarTextColor);
background-color: var(--sidebarBackgroundColorActive);
border-radius: 0;
border-left: 6px solid var(--sidebarBackgroundColorActive);
padding: 9px 10px 9px 14px;
white-space: nowrap;
overflow: hidden;
}
}
/*Sub-Menus*/
.nav-sub {
background-color: var(--sidebarBackgroundColor);
}
.sidebar .nav>li>.nav-sub>li>a {
padding-left: 25px;
color: var(--sidebarTextColor);
}
.sidebar .nav>li>.nav-sub>li>a:hover {
padding-left: 25px;
background-color: #fff;
color: #000;
}
.sidebar .nav .nav-sub li>a {
padding-right: 10px;
font-size: 0.9em;
padding: 8px 0 8px 14px;
display: block;
}
.sidebar-left .active>a,
.sidebar-left .active>a:focus,
.sidebar-left .active>a:hover {
color: var(--sidebarTextColor);
background-color: var(--sidebarBackgroundColorActive);
border-radius: 0;
border-left: 6px solid var(--sidebarActiveTab);
padding: 9px 10px 9px 14px;
}
/* END SIDE NAV */
The final bit of the jigsaw was to hide and then show the sideBar during the login process or browser refresh so that if the user had previously changed their theme from the default Sky, they didn’t get a blue sidebar until the onLogin script had run which also calls the changeTheme Global NamedAction. I did this by adding Vue.set(vueapp.$store.state.site, 'showSideNavBar', false);
at the start of both the onAppLoad and onLogin Global namedActinos and Vue.set(vueapp.$store.state.site, 'showSideNavBar', true);
at the end.
I also added the following to my BF - onLogin FileMaker script
# Get the current user's selected theme from their FileMaker record
Set Variable [ $themeSelected ; Value: GetColumn ( T14_PREFERENCES::FMBetterForms_Color ; T14_PREFERENCES::___ID_COMPANY ; $id_company ) ]
# Insert a copy of the Javascript at top of this post from my BF namedAction called changeTheme (here this is represented as #########
Insert Text [ Select ; Target: $changeTheme "#########"]
# In above Insert Text replace *var theme = app.theme* with *var theme = '___THEME___'* and then use Substitute to replace with $themeSelected
Set Variable [ $changeTheme ; Value: Substitute ( $changeTheme ; "___THEME___" ; $themeSelected ) ]
# Save the user's chosen theme to app.theme
Set Variable [ $$BF_App ; Value: JSONSetElement ( $$BF_App ; "theme" ; $themeSelected ; JSONString ) ]
# Run the changeTheme Global namedAction
Set Variable [ $$BF_Actions ; Value: BF_SetAction_function ( $changeTheme ) ]
All that’s left to do is to add additional else ifs for each colour you want to offer and copy the corresponding 50-900 colours from Tailwind. e.g. to add Indigo we’d add
//INDOGO
} else if (theme == "indigo") {
r.style.setProperty('--sidebarBackgroundColor', '#312e81'); //900
r.style.setProperty('--sidebarBackgroundColorActive', '#3730a3'); //800
r.style.setProperty('--sidebarBackgroundColorHover', '#4f46e5'); //600
r.style.setProperty('--sidebarTextColor', '#ffffff');
r.style.setProperty('--sidebarTextColor', '#dddddd');
r.style.setProperty('--sidebarActiveTab', '#a5b4fc'); //300
tailwind.config = {
theme: {
extend: {
colors: {
theme50: '#eef2ff',
theme100: '#e0e7ff',
theme200: '#c7d2fe',
theme300: '#a5b4fc',
theme400: '#818cf8',
theme500: '#6366f1',
theme600: '#4f46e5',
theme700: '#4338ca',
theme800: '#3730a3',
theme900: '#312e81'
},
}
}
}
Just remember to update both changeTheme Global namedAction and the Insert text in BF - onLogin FileMaker script.
Happy theming!!