Letting Users Customise CSS Styles in your WordPress Theme using a Settings Page

Creating a WordPress theme can be a simple process. WordPress only needs a couple of files to understand and render your theme without any issues. But as your theme grows larger and more complex, you may need to allow users to customise aspects of it that are difficult to implement using WordPress’ core Customizer API. Font choices, rich colour schemes, shadows and border rounding are all things you may need a bit more control over than you’ll find in the default tooling, which is why many themes choose to add their own Theme Settings page so that admins can bring their own branding to a site. This article will walk through how I manage users customising core styling choices.

The Plan

We’re going to be creating a custom admin page to let users make styling choices. When they submit the form with all those options, we’ll programmatically generate a CSS stylesheet to reflect those choices. That stylesheet is enqueued on every page after our base styles and resets so that it’ll override them.

Caveats & Planning Ahead

On top of our existing theme stylesheet/s, we’re going to be enqueuing a CSS stylesheet that’s unique to each website. Overriding hundreds of rules from the original stylesheet is going to not only increase our file sizes but it’s going to make our code incredibly hard to maintain as we continue development. Luckily, CSS has a perfect solution for this:

Custom Properties, also known as CSS Variables mean we can define some default options for our styles and then override them later. We can have a CSS variable for our heading and body fonts, colour palette, shadow styles and many more. We can even have a single middle value for border radius and then use calc() to add our larger and smaller sizes. Then we can create presets for none, small, medium and large border-radius styles by setting only a single value.

So, in our base file we’ll have some CSS like this:

    --color-secondary-50: 245 250 255;
    --color-secondary-100: 187 223 255;
    --color-secondary-300: 116 190 255;
    --color-secondary-500: 30 146 248;
    --color-secondary-700: 0 97 183;
    --color-secondary-900: 13 36 70;

    --color-primary-50: 247 249 237;
    --color-primary-100: 231 237 204;
    --color-primary-300: 196 214 115;
    --color-primary-500: 133 167 0;
    --color-primary-700: 92 114 3;
    --color-primary-900: 18 68 0;

    --color-black-100: 255 255 255;
    --color-black-200: 247 252 255;
    --color-black-300: 228 235 239;
    --color-black-400: 204 216 223;
    --color-black-500: 173 186 194;
    --color-black-600: 126 142 151;
    --color-black-700: 88 108 119;
    --color-black-800: 53 73 85;
    --color-black-900: 14 37 51;

    --font-heading: Roboto Slab, 'Roboto Slab';
    --font-body: Muli, 'Muli';

    --border-r: 0.2rem;
    --border-r-sm: calc(var(--border-r) * 0.6); /* 0.12rem */
    --border-r-md: calc(var(--border-r) * 1.9); /* 0.38rem */
    --border-r-lg: calc(var(--border-r) * 2.5); /* 0.5rem */


And then we can simply override that with our user’s custom styles by declaring the property. You can extend this a lot further to use CSS variables for things like heading styles but in this instance, I’m just writing new selectors.

Reading and writing files in PHP is always something that makes me slightly uncomfortable. There are a lot of things that can go wrong with permissions and managing file paths. An alternative to this might be to inline all our custom styles in the header of every page. However, this will eliminate the browser’s ability to cache our stylesheet which will increase our page load by a tiny amount for returning visitors. It’ll also increase the amount of work our server has to do on every page load. By creating a CSS file, we can make it so the server only does that work whenever the options are updated. I haven’t used the WordPress Filesystem API for this article but I would highly recommend it as it’ll handle many of the permissions and host issues for us.

Creating an Admin Page

I’ve already detailed how I create admin pages in WordPress using wrapper classes to contain all the functionality and remove the boilerplate. Take a look at that article to learn more, but we’re basically just using the built-in add_menu_page function. Setting up menu pages in WordPress is pretty painless but these classes let us do it with less code.

Checking if a WordPress Admin Page has been Submitted

Now that we’ve added our admin page, we can set up a function to check whenever our options are updated. I wasn’t able to find a great hook for this, so I’m simply firing this function on every admin_init call and checking if the options are in the $_POST variable and we’re updating the correct page, like so:

function wrds_on_theme_styling_save()
    if (
        !(array_key_exists('option_page', $_REQUEST) &&
            $_REQUEST['option_page'] == "our_custom_styling_page" &&
    ) {
        return false;


    // Generate out stylesheet, grabbing the values with get_option.
    // Example:
    // echo ":root{\n";
    // echo "--border-r:" . get_option("wrds_border_r", "0.2rem") . "\n";
    // echo "}\n";

    $custom_css_content = ob_get_clean();
    file_put_contents($my_filepath, $custom_css_content);
add_action('admin_init', 'wrds_on_theme_styling_save', 999, 0);

I’m using output buffering here so I can simply echo out all my styling rules rather than appending them to a string, I find that is just easier to manage.


Depending on your use cases you can get a lot fancier than the example I showed above. You could use a colour mixing library to generate shades from a single base colour. You might need to programmatically generate a Google Fonts URL. The benefits of this system are you get to run it once and you can be as complex as you need to be and if you set up your styles with CSS Variables from the start then you get a lot of powerful flexibility and expandability.