Auto switch Dark & Light modes of your APEX app

Switching your application theme is getting more and more popular in recent years. APEX apps and their users are no exception. Most of our devices support this feature, APEX itself introduced dark and light mode for the Application Builder too. I am going to show you a quick way of integrating it into your application.


Possible use cases of theme switching

  • Manual option for the user to switch between dark and light mode
  • Automatic theme switch, based on your device (laptop, smartphone, etc.) settings
  • Automatic theme switch, based on the time of the day (light during the day and dark during the night)
  • A hybrid solution, where user can manually switch or select some of the automatic switch settings

This tutorial will cover a hybrid solution, including a manual change of the Theme Style and an automatic one, based on user’s device settings.

Possible approaches to the task

  • The first approach will be to create two seperate Theme Styles in your app, with one global.css file for the common styles. The style changes will be detected on page load, using Dynamic Actions and User’s preferences will be kept using Application Settings. This approach is described in the steps below. For the other approaches, there will be another blog post, going step-by-step.
  • The second approach would be with one Theme style only, but different CSS files for the Light and Dark theme. For example one common global.css (like in the previous approach) and two more files – light.css and dark.css. Depending on the themes selected, only one of the two files is loaded dynamically. To achieve this, use apex_theme.set_session_style_css procedure – See documentation here.
  • … And a third approach – this is what APEX is using itself for changing the Builder theme style – CSS only approach, using something similar to this: https://css-tricks.com/dark-modes-with-css/. In this case you will have only one global.css file, but have the theme detection inside:

CSS Only Approach

  • Create a file, named global.css in Shared Components > Static Application Files. The path to your file could be something like #APP_IMAGES#css/global.css. Put the following styles inside.
/* Dark mode */
@media (prefers-color-scheme: dark) {
    body {
        background-color: black;
        color: white;
    }
}

/* Light mode */
@media (prefers-color-scheme: light) {
    body {
        background-color: white;
        color: black;
    }
}
  • Go to Edit Application Properties, switch to User Interface tab, scroll down to Cascading Style Sheets section and add the following line – #APP_IMAGES#css/global#MIN#.css. This will include your CSS file on every page of the app. What the #MIN# part is doing is switching between the regular and minified version of the CSS file, depending if you are logged into the Builder or not. If you are logged in as a developer, you will be able to view the regular CSS and easily debug it. If you are just a user of the app, you will be served the .min.css version, which is compressed, takes less space and loads faster, but is not suitable for development and debug purposes. All that is a new feature of Oracle APEX, which is very cool and I highly recommend using the #MIN# syntax.

With this setup in place, your application will switch your background colour and text colour, as soon as the colour scheme of your device is changed – no waiting, an immediate change! Cool!

Switch two Theme Styles Approach

1. Create two different theme styles for your app

I have chosen the approach with one, common for all the styles, CSS file in Static Application Files, called global.css and two seperate theme styles, created using Theme RollerLight Theme and Dark Theme.

Global.css would keep all the custom CSS we have in our app, except for the colour specific styles. They will be kept in the Custom CSS part of the Light and Dark theme in Theme Roller.

2. Create Application Settings

The Application settings will help keeping the User preferences for Theme Style Mode and Theme Style Name. To create new application settings, go to Shared Components > Application Settings. Then create two new settings, named THEME_STYLE_MODE and THEME_STYLE_NAME.

The Application settings are read and written using the following methods:

apex_app_setting.get_value('THEME_STYLE_MODE'); 
apex_app_setting.set_value('THEME_STYLE_MODE',:P0_THEME_STYLE_MODE);

3. Create Application Items

From Shared Components > Application Items, create two new items – G_THEME_STYLE_MODE and G_THEME_STYLE_NAME.

4. Create Application Computations

The Application items we created on step 3, will be populated with default values, when the user is logged into the application. Those values will be taken from user’s preferences which we created in step 2.

  • Computation Item – G_THEME_STYLE_MODE
  • Computation Point – After Authentication
  • Computation Type – Expression
  • Language – PL/SQL
  • Computation –
apex_app_setting.get_value('THEME_STYLE_MODE');

  • Computation Item – G_THEME_STYLE_NAME
  • Computation Point – After Authentication
  • Computation Type – Expression
  • Language – PL/SQL
  • Computation –
apex_app_setting.get_value('THEME_STYLE_NAME');

5. Create Application Processes

We will need one Application Process to check if a Theme Style has been changed by a Dynamic Action on page 0. If so, the process will change a flag and indicate that a reload is needed.

  • checkThemeRefresh A Radion Group type again, with two static values – Dark Theme and Light Theme. And a default value – G_THEME_STYLE_NAME.

Server-side condition will be the following: Process Point : Ajax Callback Name : checkThemeRefresh Language: PL/SQL Code:

declare
   l_theme   varchar2(100) := apex_application.g_x01;
   l_changed varchar2(3);
begin
    APEX_DEBUG.ENABLE (p_level => 2);

   case 
    when l_theme = 'Clay Lacy Dark' and :P0_CURRENT_THEME_CHANGED = 'Yes' then
        :P0_CURRENT_THEME_STYLE := 'Light Theme'; 
        :P0_THEME_STYLE_NAME    := 'Light Theme'; 
    when l_theme = 'Light Theme' and :P0_CURRENT_THEME_CHANGED = 'Yes' then
        :P0_CURRENT_THEME_STYLE := 'Dark Theme';  
        :P0_THEME_STYLE_NAME    := 'Dark Theme';
    else :P0_CURRENT_THEME_STYLE := l_theme;
   end case;  

   l_changed := :P0_CURRENT_THEME_CHANGED;
   apex_debug.info(':P0_CURRENT_THEME_CHANGED -> '||l_changed);

    apex_json.open_object; 
    apex_json.write('success', true); 
    apex_json.write('p_refresh_theme', l_changed); 
    apex_json.close_object;   
    
    APEX_DEBUG.DISABLE;

end;

6. On Page 0, create some global regions and Dynamic Actions

  • P0_THEME_STYLE_MODE It can be a Radion Group type, with two static values – Dark Theme and Light Theme. And a default value – G_THEME_STYLE_MODE.

  • P0_THEME_STYLE_NAME A Radion Group type again, with two static values – Dark Theme and Light Theme. And a default value – G_THEME_STYLE_NAME.

Server-side condition will be the following:

Type : Item = Value
Item : P0_THEME_STYLE_NAME
Value: manual

This way, the item will be hidden if we are not selecting the Theme Style manually.

  • P0_CURRENT_THEME_CHANGED and P0_CURRENT_THEME_STYLE Those will help us know if the theme has changed. They are later used in some of the Dynamic Actions. Their type is Display Only, because otherwise (as a hidden item for example) they will not be available with using JavaScript (and we nwill need that).

7. On Page 0, create some Dynamic Actions

  • onLoad - Auto switch Theme Style (device)

Event : Page Load

Client-side Condition
Type : Javascript expression
Javascript expression :

window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches

Server-side Condition
Type : Item = Value
Item : G_THEME_STYLE_MODE
Value: device

True

  • Action : Execute Server-side Code
    Language : PL/SQL
    PL/SQL Code:
declare
    l_current_Theme_style_name apex_application_theme_styles.name%type;
begin

select s.name theme_style_name
  into l_current_Theme_style_name
  from apex_application_theme_styles s
 where s.Theme_style_id = apex_theme.get_user_style( :APP_ID, :APP_USER, 42 ); 

for c1 in (select   t.theme_number,
                    s.name theme_style_name,
                    s.theme_style_id,
                    s.is_current is_current
             from apex_application_theme_styles s, apex_application_themes t
            where s.application_id = t.application_id
              and s.theme_number = t.theme_number
              and s.application_id = :app_id
              and lower(s.name) = 'dark theme'  
        order by 1 )
    loop

          apex_theme.set_user_style (
            p_application_id => :APP_ID,
            p_user           => :APP_USER,
            p_theme_number   => c1.theme_number,
            p_id             => c1.theme_style_id ); 

        if lower(l_current_Theme_style_name)  'dark theme' then
            :P0_CURRENT_THEME_CHANGED := 'Yes';
            :P0_CURRENT_THEME_STYLE   := 'Dark Theme';    
        else 
            :P0_CURRENT_THEME_CHANGED := 'No';   
        end if;  

        :P0_THEME_STYLE_NAME          := 'Dark Theme';

    end loop;

end;
  • Action : Execute JavaScript Code
    Language : PL/SQL
    PL/SQL Code:
apex.server.process("checkThemeRefresh",       // Process or AJAX Callback name
        {x01: "Clay Lacy Dark"},  // Parameter "x01"
        {
        success: function (pData) {             // Success Javascript
            if (pData.p_refresh_theme === 'Yes') {
                apex.submit();
                console.log("Theme style changed from Light Mode to Dark Mode. Page was refreshed.");
                console.log("Your application is now in Dark Mode."); 
            } else {
                console.log("pData.p_refresh_theme -> " + pData.p_refresh_theme);
            }
         }
       }
   );

False

  • Action : Execute Server-side Code
    Language : PL/SQL
    PL/SQL Code:
declare
    l_current_Theme_style_name apex_application_theme_styles.name%type;
begin

select s.name theme_style_name
  into l_current_Theme_style_name
  from apex_application_theme_styles s
 where s.Theme_style_id = apex_theme.get_user_style( :APP_ID, :APP_USER, 42 ); 

for c1 in (select   t.theme_number,
                    s.name theme_style_name,
                    s.theme_style_id,
                    s.is_current is_current
             from apex_application_theme_styles s, apex_application_themes t
            where s.application_id = t.application_id
              and s.theme_number = t.theme_number
              and s.application_id = :app_id
              and lower(s.name) = 'light theme'  
        order by 1 )
    loop

        apex_theme.set_user_style (
            p_application_id => :APP_ID,
            p_user           => :APP_USER,
            p_theme_number   => c1.theme_number,
            p_id             => c1.theme_style_id );    

        if lower(l_current_Theme_style_name)  'light theme' then
            :P0_CURRENT_THEME_CHANGED := 'Yes';
            :P0_CURRENT_THEME_STYLE   := 'Light Theme'; 
        else 
            :P0_CURRENT_THEME_CHANGED := 'No';    
        end if;  

        :P0_THEME_STYLE_NAME          := 'Light Theme'; 

    end loop;
 
 APEX_DEBUG.ENABLE;
 apex_debug.info('APP_ID -> '||:APP_ID); 
 apex_debug.info('APP_USER -> '||:APP_USER);  
 APEX_DEBUG.DISABLE;

 end;   
  • Action : Execute JavaScript Code
    Language : PL/SQL
    PL/SQL Code:
apex.server.process("checkThemeRefresh",  // Process or AJAX Callback name
    {x01: "Light Theme"},       // Parameter "x01"
    {
      success: function (pData) {             // Success Javascript
        if (pData.p_refresh_theme === 'Yes') {
            apex.submit();
            console.log("pData.p_refresh_theme -> " + pData.p_refresh_theme);
            console.log("Theme style changed from Dark Mode to Light Mode. Page was refreshed.");
            console.log("Your application is now in Light Mode.");
        } else {
            console.log("pData.p_refresh_theme -> " + pData.p_refresh_theme);
        }
      }
    }
  ); 

  • onChange - Switch theme style name

Event : Change
Selection Type : Item(s)
Item(s) : P0_THEME_STYLE_NAME

True

  • Action : Execute Server-side Code
    Language : PL/SQL
    PL/SQL Code:
begin

apex_app_setting.set_value('THEME_STYLE_NAME',:P0_THEME_STYLE_NAME);
apex_util.set_session_state('G_THEME_STYLE_NAME', :P0_THEME_STYLE_NAME);

for c1 in (select   t.theme_number,
                    s.name theme_style_name,
                    s.theme_style_id,
                    s.is_current is_current
             from apex_application_theme_styles s, apex_application_themes t
            where s.application_id = t.application_id
              and s.theme_number = t.theme_number
              and s.application_id = :app_id
            and lower(s.name) = lower(:P0_THEME_STYLE_NAME)  
        order by 1 )
    loop

          apex_theme.set_user_style (
            p_application_id => :APP_ID,
            p_user           => :APP_USER,
            p_theme_number   => c1.theme_number,
            p_id             => c1.theme_style_id ); 

            APEX_DEBUG.ENABLE (p_level => 2);
            apex_debug.info(':P0_CURRENT_THEME_CHANGED -> '||:P0_CURRENT_THEME_CHANGED);
            apex_debug.info(':P0_THEME_STYLE_NAME -> '||:P0_THEME_STYLE_NAME);
            apex_debug.info('theme_style_name -> '||c1.theme_style_name);
            APEX_DEBUG.DISABLE;

    end loop;

end;

Items to Submit : P0_THEME_STYLE_NAME
Items to Return : P0_CURRENT_THEME_CHANGED,P0_CURRENT_THEME_STYLE,G_THEME_STYLE_NAME

  • Action : Submit Page

  • onChange - Switch theme style mode

Event : Change
Selection Type : Item(s)
Item(s) : P0_THEME_STYLE_MODE

True

  • Action : Execute Server-side Code
    Language : PL/SQL
    PL/SQL Code:
apex_app_setting.set_value('THEME_STYLE_MODE',:P0_THEME_STYLE_MODE);
apex_util.set_session_state('G_THEME_STYLE_MODE', :P0_THEME_STYLE_MODE);

Items to Submit : P0_THEME_STYLE_MODE
Items to Return : G_THEME_STYLE_MODE

  • Action : Submit Page

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s