4 Min. read

How to Style Slate Widgets Using Style Sets

When working with Slate in Unreal Engine it’s often needed to share styles between multiple widgets or easily reference content (such as images). A quick way to do this is to store styles in a FSlateStyleSet object.

Creating a Style Set

Style sets can be defined virtually anywhere, but the pattern I like to use is to wrap the style set into a class that provides access to it in a singleton-like way.

MyEditorStyle.h
#pragma once
#include "CoreMinimal.h"
#include "Styling/SlateStyle.h"
 
class FMyEditorStyle final
{
public:
 
    static void Initialize();
    static void Shutdown();
 
    static ISlateStyle& Get()
    {
        return *StyleSet.Get();
    }
 
    static FName GetStyleSetName();
 
private:
 
    static TUniquePtr<FSlateStyleSet> StyleSet;
};
MyEditorStyle.cpp
#include "MyEditorStyle.h"
#include "Styling/CoreStyle.h"
#include "Styling/SlateStyleRegistry.h"
 
TUniquePtr<FSlateStyleSet> FMyEditorStyle::StyleSet;
 
void FMyEditorStyle::Initialize()
{
    if (StyleSet.IsValid())
    {
        // Only set up once
        return;
    }
 
    // Create the style sheet
    StyleSet = MakeUnique<FSlateStyleSet>(GetStyleSetName());
    
    // TODO: define styles here :)
 
    // Register the style set
    FSlateStyleRegistry::RegisterSlateStyle(*StyleSet.Get());
}
 
void FMyEditorStyle::Shutdown()
{
    // Unregister the style set
    if (StyleSet.IsValid())
    {
        FSlateStyleRegistry::UnRegisterSlateStyle(*StyleSet.Get());
        StyleSet.Reset();
    }
}
 
FName FMyEditorStyle::GetStyleSetName()
{
    static const FName StyleSetName(TEXT("MyEditorStyle"));
    return StyleSetName;
}

The Initialize and Shutdown methods should be called when the module that contains the style set is loaded and unloaded. This ensures that our style set exists and can be accessed safely as long as its module is loaded.

MyEditorModule.cpp
#include "MyEditorStyle.h"
#include "Modules/ModuleManager.h"
 
class FMyEditorModule final : public IModuleInterface
{
public:
    void StartupModule() override
    {
        FMyEditorStyle::Initialize();
    }
 
    void ShutdownModule() override
    {
        FMyEditorStyle::Shutdown();
    }
};
 
IMPLEMENT_MODULE(FMyEditorModule, MyEditorModule)

Defining styles

Style sets can contain virtually any type of styling information including brushes, margins and widget styles such as FTextBlockStyle for STextBlock, FButtonStyle for SButton, etc. Let’s look at a few examples:

void FMyEditorStyle::Initialize()
{
    // [...]
 
    // Set paths to content folders to support loading assets that ship with the engine
    StyleSet->SetContentRoot(FPaths::EngineContentDir() / TEXT("Editor/Slate"));
    StyleSet->SetCoreContentRoot(FPaths::EngineContentDir() / TEXT("Slate"));
    
    // Register some styles
    StyleSet->Set("MyTextStyle", FTextBlockStyle()
        .SetFont(FCoreStyle::GetDefaultFontStyle("Regular", 12))
        .SetColorAndOpacity(FLinearColor::Yellow)
    );
 
    StyleSet->Set("MyButtonStyle", FButtonStyle()
        .SetNormal(FSlateNoResource())
        .SetHovered(FSlateBoxBrush(RootToContentDir("Common/ButtonHoverHint.png"), FMargin(4.0f), FLinearColor::White))
        .SetPressed(FSlateBoxBrush(RootToContentDir("Common/ButtonHoverHint.png"), FMargin(4.0f), FLinearColor::White))
    );
 
    // Register some brushes
    const FVector2D Icon16x16(16.0f, 16.0f);
    const FVector2D Icon20x20(20.0f, 20.0f);
 
    StyleSet->Set("ContentBrowserIcon", new FSlateVectorImageBrush(RootToContentDir("Starship/Common/ContentBrowser.svg"), Icon20x20));
    StyleSet->Set("ContentBrowserIcon.Small", new FSlateVectorImageBrush(RootToContentDir("Starship/Common/ContentBrowser.svg"), Icon16x16));
 
    // [...]
}

Since defining brushes can become fairly verbose, most stylesheets in the engine use macros to cut down on the boilerplate:

#define BOX_BRUSH(RelativePath, ...) FSlateBoxBrush(StyleSet->RootToContentDir(RelativePath, TEXT(".svg")), __VA_ARGS__)
#define IMAGE_BRUSH_SVG(RelativePath, ...) FSlateVectorImageBrush(StyleSet->RootToContentDir(RelativePath, TEXT(".svg")), __VA_ARGS__)
 
void FMyEditorStyle::Initialize()
{
    // [...]
 
    StyleSet->Set("MyButtonStyle", FButtonStyle()
        .SetNormal(FSlateNoResource())
        .SetHovered(BOX_BRUSH("Common/ButtonHoverHint", FMargin(4.0f), FLinearColor::White))
        .SetPressed(BOX_BRUSH("Common/ButtonHoverHint", FMargin(4.0f), FLinearColor::White))
    );
 
    StyleSet->Set("ContentBrowserIcon", new IMAGE_BRUSH_SVG("Starship/Common/ContentBrowser", Icon20x20));
    StyleSet->Set("ContentBrowserIcon.Small", new IMAGE_BRUSH_SVG("Starship/Common/ContentBrowser", Icon16x16));
 
    // [...]
}
 
#undef BOX_BRUSH
#undef IMAGE_BRUSH_SVG

Whether to use this approach really boils down to personal preference. Regardless of that, let’s take a moment to appreciate the fact that Unreal Engine 5 finally supports loading SVG files. Very nice!

A GIF showing Dwayne Johnson nodding

Using images from a plugin or the project

So far we’ve only used assets that come with the engine, but what if we wanted to use custom assets? The first step is to put them in the plugin’s (or project’s) Content folder — or Content/Slate in case we want to make the content available in cooked builds.

After that, it’s simply a matter of referencing our content. To do this, let’s define two additional helpers on our style set:

MyEditorStyle.h
#pragma once
#include "CoreMinimal.h"
#include "Styling/SlateStyle.h"
 
class FMyEditorStyle final
{
    // [...]
private:
 
    static FString RootToPluginContentDir(const FString& RelativePath, const TCHAR* Extension);
    static FString RootToProjectContentDir(const FString& RelativePath, const TCHAR* Extension);
 
    // [...]
};
MyEditorStyle.cpp
// [...]
 
FString FMyEditorStyle::RootToPluginContentDir(const FString& RelativePath, const TCHAR* Extension)
{
    static const FString ContentDir = IPluginManager::Get().FindPlugin(TEXT("MyPlugin"))->GetContentDir();
    return (ContentDir / RelativePath) + Extension;
}
 
FString FMyEditorStyle::RootToProjectContentDir(const FString& RelativePath, const TCHAR* Extension)
{
    static const FString ContentDir = FPaths::ProjectConfigDir();
    return (ContentDir / RelativePath) + Extension;
}

These functions can be used directly or wrapped in macros similar to the ones above:

#define IMAGE_PLUGIN_BRUSH_SVG(RelativePath, ...) FSlateVectorImageBrush(RootToPluginContentDir(RelativePath, TEXT(".svg")), __VA_ARGS__)
 
void FMyEditorStyle::Initialize()
{
    // [...]
 
    StyleSet->Set("MyCustomProjectIcon", new FSlateVectorImageBrush(RootToContentDir("MyCustomProjectIcon.svg"), Icon16x16));
    StyleSet->Set("MyCustomPluginIcon", new IMAGE_PLUGIN_BRUSH_SVG("MyCustomPluginIcon", Icon16x16));
 
    // [...]
}
 
#undef IMAGE_PLUGIN_BRUSH_SVG

Using styles

Styles defined via style sets can be accessed directly using one of the corresponding getters, such as GetBrush, GetMargin, etc. Some widgets also support specifying the styleset and the style name directly, without having to call one of the getter functions.

// Variables
const FSlateBrush* Brush = FMyEditorStyle::Get().GetBrush("MyCustomProjectIcon");
const FSlateIcon Icon(FMyEditorStyle::GetStyleSetName(), "MyCustomProjectIcon");
 
// Widgets
SNew(SImage)
.Image(FMyEditorStyle::Get().GetBrush("MyCustomProjectIcon"));
 
SNew(STextBlock)
.TextStyle(FMyEditorStyle::Get(), "MyTextStyle");
 
SNew(SButton)
.ButtonStyle(FMyEditorStyle::Get(), "MyButtonStyle");

Bonus: using Unreal Editor styles

It’s interesting to note that the whole editor is styled via style sets. You can find the most recent styles (added in UE5) in FStarshipEditorStyle, but some styles can still be found in legacy style sets from the UE4 era, such as FSlateEditorStyle.

Regardless of where they’re defined, built-in styles can be accessed via FAppStyle, for example:

const FSlateBrush* FindInContentBrowserBrush = FAppStyle::Get().GetBrush("SystemWideCommands.FindInContentBrowser");