Repository: microsoft/terminal (language: cpp)

Issue:
Light tab color causes unreadably dark foreground (text) color when the tab is not focused
### Windows Terminal version

1.12.10982.0

### Windows build number

Microsoft Windows [Version 10.0.19043.1706]

### Other Software

None

### Steps to reproduce

1. Start MS terminal
2. Open a total of 2+ tabs
3. Make sure that you're running a 'Dark' theme
4. Set the color of a tab to be bright enough that the foreground text switches to black (the 'Teal' option seems to work well - it's custom color is `#05FFEE`)
5. Focus a tab (let's call it "Tab A") and bask in the readable text.  The tab color is that nice, bright teal and the text has automatically been made black, an easy-to-read, high contrast color.
6. Switch to a different tab.  Notice that Tab A's tab color is now very dark / dim (which is a very reasonable way to convey to the user that the tab isn't currently focused).  Notice also that the text color is still black, and thus unreadable.
7. Look upon incredibly low contrast, unfocused Tab A and weep, for there is no possible way to read that text.

### Expected Behavior

I'd love to be able to read the text on my unfocused tabs, even if I choose a light tab color for them.

I *think* what might solve this is: when a tab becomes unfocused and you dim the tab go back and adjust the foreground (text) color to be white.

### Actual Behavior

Foreground color remains black.

I can't read black text on a really, really dark tab.

Oh boy is it hard to see what 

Cue the weeping :)


Discussion / hints:

![image](https://user-images.githubusercontent.com/18356694/172600385-0eca6d48-26ac-410c-b7dc-1a5784646ee7.png)


Shouldn't be too hard to set the unfocused text color based on the contrast with the unfocused color. Tricks will be:
* Do we actually control the unfocused color in this scenario? Or are we just letting WinUI apply some default amount of transparency on top of the tab view item
* Does the TabViewItem actually have a resource for it's unfocused text color, separate from the focused tab text color?

Fix patch:
diff --git a/src/cascadia/TerminalApp/App.xaml b/src/cascadia/TerminalApp/App.xaml
index 2f025073f97..7dabb689a3c 100644
--- a/src/cascadia/TerminalApp/App.xaml
+++ b/src/cascadia/TerminalApp/App.xaml
@@ -153,6 +153,9 @@
 
                             <StaticResource x:Key="UnfocusedBorderBrush"
                                             ResourceKey="ApplicationPageBackgroundThemeBrush" />
+
+                            <SolidColorBrush x:Key="SettingsUiTabBrush"
+                                             Color="#0c0c0c" />
                         </ResourceDictionary>
 
                         <ResourceDictionary x:Key="Light">
@@ -166,6 +169,9 @@
 
                             <StaticResource x:Key="UnfocusedBorderBrush"
                                             ResourceKey="ApplicationPageBackgroundThemeBrush" />
+
+                            <SolidColorBrush x:Key="SettingsUiTabBrush"
+                                             Color="#ffffff" />
                         </ResourceDictionary>
 
                         <ResourceDictionary x:Key="HighContrast">
@@ -183,6 +189,9 @@
                             -->
                             <StaticResource x:Key="TabViewBackground"
                                             ResourceKey="SystemColorButtonFaceColorBrush" />
+
+                            <StaticResource x:Key="SettingsUiTabBrush"
+                                            ResourceKey="SystemControlBackgroundBaseLowBrush" />
                         </ResourceDictionary>
 
                     </ResourceDictionary.ThemeDictionaries>
diff --git a/src/cascadia/TerminalApp/SettingsTab.cpp b/src/cascadia/TerminalApp/SettingsTab.cpp
index 54e48ed1e0d..6ac9bb5e79e 100644
--- a/src/cascadia/TerminalApp/SettingsTab.cpp
+++ b/src/cascadia/TerminalApp/SettingsTab.cpp
@@ -23,9 +23,11 @@ namespace winrt
 
 namespace winrt::TerminalApp::implementation
 {
-    SettingsTab::SettingsTab(MainPage settingsUI)
+    SettingsTab::SettingsTab(MainPage settingsUI,
+                             winrt::Windows::UI::Xaml::ElementTheme requestedTheme)
     {
         Content(settingsUI);
+        _requestedTheme = requestedTheme;
 
         _MakeTabViewItem();
         _CreateContextMenu();
@@ -36,6 +38,10 @@ namespace winrt::TerminalApp::implementation
     {
         auto settingsUI{ Content().as<MainPage>() };
         settingsUI.UpdateSettings(settings);
+
+        // Stash away the current requested theme of the app. We'll need that in
+        // _BackgroundBrush() to do a theme-aware resource lookup
+        _requestedTheme = settings.GlobalSettings().CurrentTheme().RequestedTheme();
     }
 
     // Method Description:
@@ -105,4 +111,16 @@ namespace winrt::TerminalApp::implementation
             TabViewItem().IconSource(IconPathConverter::IconSourceMUX(glyph));
         }
     }
+
+    winrt::Windows::UI::Xaml::Media::Brush SettingsTab::_BackgroundBrush()
+    {
+        // Look up the color we should use for the settings tab item from our
+        // resources. This should only be used for when "terminalBackground" is
+        // requested.
+        static const auto key = winrt::box_value(L"SettingsUiTabBrush");
+        // You can't just do a Application::Current().Resources().TryLookup
+        // lookup, cause the app theme never changes! Do the hacky version
+        // instead.
+        return ThemeLookup(Application::Current().Resources(), _requestedTheme, key).try_as<winrt::Windows::UI::Xaml::Media::Brush>();
+    }
 }
diff --git a/src/cascadia/TerminalApp/SettingsTab.h b/src/cascadia/TerminalApp/SettingsTab.h
index 803ed8cb03e..e9cfc9c8376 100644
--- a/src/cascadia/TerminalApp/SettingsTab.h
+++ b/src/cascadia/TerminalApp/SettingsTab.h
@@ -24,7 +24,8 @@ namespace winrt::TerminalApp::implementation
     struct SettingsTab : SettingsTabT<SettingsTab, TabBase>
     {
     public:
-        SettingsTab(winrt::Microsoft::Terminal::Settings::Editor::MainPage settingsUI);
+        SettingsTab(winrt::Microsoft::Terminal::Settings::Editor::MainPage settingsUI,
+                    winrt::Windows::UI::Xaml::ElementTheme requestedTheme);
 
         void UpdateSettings(Microsoft::Terminal::Settings::Model::CascadiaSettings settings);
         void Focus(winrt::Windows::UI::Xaml::FocusState focusState) override;
@@ -32,7 +33,11 @@ namespace winrt::TerminalApp::implementation
         std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs> BuildStartupActions() const override;
 
     private:
+        winrt::Windows::UI::Xaml::ElementTheme _requestedTheme;
+
         void _MakeTabViewItem() override;
         winrt::fire_and_forget _CreateIcon();
+
+        virtual winrt::Windows::UI::Xaml::Media::Brush _BackgroundBrush() override;
     };
 }
diff --git a/src/cascadia/TerminalApp/TabBase.cpp b/src/cascadia/TerminalApp/TabBase.cpp
index 167b5008066..dda15f10bc8 100644
--- a/src/cascadia/TerminalApp/TabBase.cpp
+++ b/src/cascadia/TerminalApp/TabBase.cpp
@@ -5,6 +5,8 @@
 #include <LibraryResources.h>
 #include "TabBase.h"
 #include "TabBase.g.cpp"
+#include "Utils.h"
+#include "ColorHelper.h"
 
 using namespace winrt;
 using namespace winrt::Windows::UI::Xaml;
@@ -252,4 +254,291 @@ namespace winrt::TerminalApp::implementation
         });
     }
 
+    std::optional<winrt::Windows::UI::Color> TabBase::GetTabColor()
+    {
+        return std::nullopt;
+    }
+
+    void TabBase::ThemeColor(const winrt::Microsoft::Terminal::Settings::Model::ThemeColor& focused,
+                             const winrt::Microsoft::Terminal::Settings::Model::ThemeColor& unfocused,
+                             const til::color& tabRowColor)
+    {
+        _themeColor = focused;
+        _unfocusedThemeColor = unfocused;
+        _tabRowColor = tabRowColor;
+        _RecalculateAndApplyTabColor();
+    }
+
+    // Method Description:
+    // - This function dispatches a function to the UI thread to recalculate
+    //   what this tab's current background color should be. If a color is set,
+    //   it will apply the given color to the tab's background. Otherwise, it
+    //   will clear the tab's background color.
+    // Arguments:
+    // - <none>
+    // Return Value:
+    // - <none>
+    void TabBase::_RecalculateAndApplyTabColor()
+    {
+        auto weakThis{ get_weak() };
+
+        TabViewItem().Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [weakThis]() {
+            auto ptrTab = weakThis.get();
+            if (!ptrTab)
+            {
+                return;
+            }
+
+            auto tab{ ptrTab };
+
+            // GetTabColor will return the color set by the color picker, or the
+            // color specified in the profile. If neither of those were set,
+            // then look to _themeColor to see if there's a value there.
+            // Otherwise, clear our color, falling back to the TabView defaults.
+            const auto currentColor = tab->GetTabColor();
+            if (currentColor.has_value())
+            {
+                tab->_ApplyTabColorOnUIThread(currentColor.value());
+            }
+            else if (tab->_themeColor != nullptr)
+            {
+                // Safely get the active control's brush.
+                const Media::Brush terminalBrush{ tab->_BackgroundBrush() };
+
+                if (const auto themeBrush{ tab->_themeColor.Evaluate(Application::Current().Resources(), terminalBrush, false) })
+                {
+                    // ThemeColor.Evaluate will get us a Brush (because the
+                    // TermControl could have an acrylic BG, for example). Take
+                    // that brush, and get the color out of it. We don't really
+                    // want to have the tab items themselves be acrylic.
+                    tab->_ApplyTabColorOnUIThread(til::color{ ThemeColor::ColorFromBrush(themeBrush) });
+                }
+                else
+                {
+                    tab->_ClearTabBackgroundColor();
+                }
+            }
+            else
+            {
+                tab->_ClearTabBackgroundColor();
+            }
+        });
+    }
+
+    // Method Description:
+    // - Applies the given color to the background of this tab's TabViewItem.
+    // - Sets the tab foreground color depending on the luminance of
+    // the background color
+    // - This method should only be called on the UI thread.
+    // Arguments:
+    // - color: the color the user picked for their tab
+    // Return Value:
+    // - <none>
+    void TabBase::_ApplyTabColorOnUIThread(const winrt::Windows::UI::Color& color)
+    {
+        Media::SolidColorBrush selectedTabBrush{};
+        Media::SolidColorBrush deselectedTabBrush{};
+        Media::SolidColorBrush fontBrush{};
+        Media::SolidColorBrush deselectedFontBrush{};
+        Media::SolidColorBrush secondaryFontBrush{};
+        Media::SolidColorBrush hoverTabBrush{};
+        Media::SolidColorBrush subtleFillColorSecondaryBrush;
+        Media::SolidColorBrush subtleFillColorTertiaryBrush;
+        // calculate the luminance of the current color and select a font
+        // color based on that
+        // see https://www.w3.org/TR/WCAG20/#relativeluminancedef
+        if (TerminalApp::ColorHelper::IsBrightColor(color))
+        {
+            fontBrush.Color(winrt::Windows::UI::Colors::Black());
+            auto secondaryFontColor = winrt::Windows::UI::Colors::Black();
+            // For alpha value see: https://github.com/microsoft/microsoft-ui-xaml/blob/7a33ad772d77d908aa6b316ec24e6d2eb3ebf571/dev/CommonStyles/Common_themeresources_any.xaml#L269
+            secondaryFontColor.A = 0x9E;
+            secondaryFontBrush.Color(secondaryFontColor);
+            auto subtleFillColorSecondary = winrt::Windows::UI::Colors::Black();
+            subtleFillColorSecondary.A = 0x09;
+            subtleFillColorSecondaryBrush.Color(subtleFillColorSecondary);
+            auto subtleFillColorTertiary = winrt::Windows::UI::Colors::Black();
+            subtleFillColorTertiary.A = 0x06;
+            subtleFillColorTertiaryBrush.Color(subtleFillColorTertiary);
+        }
+        else
+        {
+            fontBrush.Color(winrt::Windows::UI::Colors::White());
+            auto secondaryFontColor = winrt::Windows::UI::Colors::White();
+            // For alpha value see: https://github.com/microsoft/microsoft-ui-xaml/blob/7a33ad772d77d908aa6b316ec24e6d2eb3ebf571/dev/CommonStyles/Common_themeresources_any.xaml#L14
+            secondaryFontColor.A = 0xC5;
+            secondaryFontBrush.Color(secondaryFontColor);
+            auto subtleFillColorSecondary = winrt::Windows::UI::Colors::White();
+            subtleFillColorSecondary.A = 0x0F;
+            subtleFillColorSecondaryBrush.Color(subtleFillColorSecondary);
+            auto subtleFillColorTertiary = winrt::Windows::UI::Colors::White();
+            subtleFillColorTertiary.A = 0x0A;
+            subtleFillColorTertiaryBrush.Color(subtleFillColorTertiary);
+        }
+
+        selectedTabBrush.Color(color);
+
+        // Start with the current tab color, set to Opacity=.3
+        til::color deselectedTabColor{ color };
+        deselectedTabColor = deselectedTabColor.with_alpha(77); // 255 * .3 = 77
+
+        // If we DON'T have a color set from the color picker, or the profile's
+        // tabColor, but we do have a unfocused color in the theme, use the
+        // unfocused theme color here instead.
+        if (!GetTabColor().has_value() &&
+            _unfocusedThemeColor != nullptr)
+        {
+            // Safely get the active control's brush.
+            const Media::Brush terminalBrush{ _BackgroundBrush() };
+
+            // Get the color of the brush.
+            if (const auto themeBrush{ _unfocusedThemeColor.Evaluate(Application::Current().Resources(), terminalBrush, false) })
+            {
+                // We did figure out the brush. Get the color out of it. If it
+                // was "accent" or "terminalBackground", then we're gonna set
+                // the alpha to .3 manually here.
+                // (ThemeColor::UnfocusedTabOpacity will do this for us). If the
+                // user sets both unfocused and focused tab.background to
+                // terminalBackground, this will allow for some differentiation
+                // (and is generally just sensible).
+                deselectedTabColor = til::color{ ThemeColor::ColorFromBrush(themeBrush) }.with_alpha(_unfocusedThemeColor.UnfocusedTabOpacity());
+            }
+        }
+
+        // currently if a tab has a custom color, a deselected state is
+        // signified by using the same color with a bit of transparency
+        deselectedTabBrush.Color(deselectedTabColor.with_alpha(255));
+        deselectedTabBrush.Opacity(deselectedTabColor.a / 255.f);
+
+        hoverTabBrush.Color(color);
+        hoverTabBrush.Opacity(0.6);
+
+        // Account for the color of the tab row when setting the color of text
+        // on inactive tabs. Consider:
+        // * black active tabs
+        // * on a white tab row
+        // * with a transparent inactive tab color
+        //
+        // We don't want that to result in white text on a white tab row for
+        // inactive tabs.
+        const auto deselectedActualColor = deselectedTabColor.layer_over(_tabRowColor);
+        if (TerminalApp::ColorHelper::IsBrightColor(deselectedActualColor))
+        {
+            deselectedFontBrush.Color(winrt::Windows::UI::Colors::Black());
+        }
+        else
+        {
+            deselectedFontBrush.Color(winrt::Windows::UI::Colors::White());
+        }
+
+        // Prior to MUX 2.7, we set TabViewItemHeaderBackground, but now we can
+        // use TabViewItem().Background() for that. HOWEVER,
+        // TabViewItem().Background() only sets the color of the tab background
+        // when the TabViewItem is unselected. So we still need to set the other
+        // properties ourselves.
+        //
+        // In GH#11294 we thought we'd still need to set
+        // TabViewItemHeaderBackground manually, but GH#11382 discovered that
+        // Background() was actually okay after all.
+        TabViewItem().Background(deselectedTabBrush);
+        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundSelected"), selectedTabBrush);
+        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPointerOver"), hoverTabBrush);
+        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPressed"), selectedTabBrush);
+
+        // Similarly, TabViewItem().Foreground()  sets the color for the text
+        // when the TabViewItem isn't selected, but not when it is hovered,
+        // pressed, dragged, or selected, so we'll need to just set them all
+        // anyways.
+        TabViewItem().Foreground(deselectedFontBrush);
+        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderForeground"), deselectedFontBrush);
+        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundSelected"), fontBrush);
+        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundPointerOver"), fontBrush);
+        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundPressed"), fontBrush);
+
+        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonForeground"), deselectedFontBrush);
+        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonForegroundPressed"), secondaryFontBrush);
+        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonForegroundPointerOver"), fontBrush);
+        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderPressedCloseButtonForeground"), fontBrush);
+        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderPointerOverCloseButtonForeground"), fontBrush);
+        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderSelectedCloseButtonForeground"), fontBrush);
+        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonBackgroundPressed"), subtleFillColorTertiaryBrush);
+        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonBackgroundPointerOver"), subtleFillColorSecondaryBrush);
+
+        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewButtonForegroundActiveTab"), fontBrush);
+        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewButtonForegroundPressed"), fontBrush);
+        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewButtonForegroundPointerOver"), fontBrush);
+
+        _RefreshVisualState();
+    }
+
+    // Method Description:
+    // - Clear out any color we've set for the TabViewItem.
+    // - This method should only be called on the UI thread.
+    // Arguments:
+    // - <none>
+    // Return Value:
+    // - <none>
+    void TabBase::_ClearTabBackgroundColor()
+    {
+        static const winrt::hstring keys[] = {
+            L"TabViewItemHeaderBackground",
+            L"TabViewItemHeaderBackgroundSelected",
+            L"TabViewItemHeaderBackgroundPointerOver",
+            L"TabViewItemHeaderBackgroundPressed",
+            L"TabViewItemHeaderForeground",
+            L"TabViewItemHeaderForegroundSelected",
+            L"TabViewItemHeaderForegroundPointerOver",
+            L"TabViewItemHeaderForegroundPressed",
+            L"TabViewItemHeaderCloseButtonForeground",
+            L"TabViewItemHeaderCloseButtonForegroundPressed",
+            L"TabViewItemHeaderCloseButtonForegroundPointerOver",
+            L"TabViewItemHeaderPressedCloseButtonForeground",
+            L"TabViewItemHeaderPointerOverCloseButtonForeground",
+            L"TabViewItemHeaderSelectedCloseButtonForeground",
+            L"TabViewItemHeaderCloseButtonBackgroundPressed",
+            L"TabViewItemHeaderCloseButtonBackgroundPointerOver",
+            L"TabViewButtonForegroundActiveTab",
+            L"TabViewButtonForegroundPressed",
+            L"TabViewButtonForegroundPointerOver"
+        };
+
+        // simply clear any of the colors in the tab's dict
+        for (const auto& keyString : keys)
+        {
+            auto key = winrt::box_value(keyString);
+            if (TabViewItem().Resources().HasKey(key))
+            {
+                TabViewItem().Resources().Remove(key);
+            }
+        }
+
+        // GH#11382 DON'T set the background to null. If you do that, then the
+        // tab won't be hit testable at all. Transparent, however, is a totally
+        // valid hit test target. That makes sense.
+        TabViewItem().Background(WUX::Media::SolidColorBrush{ Windows::UI::Colors::Transparent() });
+
+        _RefreshVisualState();
+    }
+
+    // Method Description:
+    // Toggles the visual state of the tab view item,
+    // so that changes to the tab color are reflected immediately
+    // Arguments:
+    // - <none>
+    // Return Value:
+    // - <none>
+    void TabBase::_RefreshVisualState()
+    {
+        if (TabViewItem().IsSelected())
+        {
+            VisualStateManager::GoToState(TabViewItem(), L"Normal", true);
+            VisualStateManager::GoToState(TabViewItem(), L"Selected", true);
+        }
+        else
+        {
+            VisualStateManager::GoToState(TabViewItem(), L"Selected", true);
+            VisualStateManager::GoToState(TabViewItem(), L"Normal", true);
+        }
+    }
+
 }
diff --git a/src/cascadia/TerminalApp/TabBase.h b/src/cascadia/TerminalApp/TabBase.h
index a04873f8905..fb32fe0e377 100644
--- a/src/cascadia/TerminalApp/TabBase.h
+++ b/src/cascadia/TerminalApp/TabBase.h
@@ -25,6 +25,11 @@ namespace winrt::TerminalApp::implementation
         void SetActionMap(const Microsoft::Terminal::Settings::Model::IActionMapView& actionMap);
         virtual std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs> BuildStartupActions() const = 0;
 
+        virtual std::optional<winrt::Windows::UI::Color> GetTabColor();
+        void ThemeColor(const winrt::Microsoft::Terminal::Settings::Model::ThemeColor& focused,
+                        const winrt::Microsoft::Terminal::Settings::Model::ThemeColor& unfocused,
+                        const til::color& tabRowColor);
+
         WINRT_CALLBACK(RequestFocusActiveControl, winrt::delegate<void()>);
 
         WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
@@ -51,6 +56,10 @@ namespace winrt::TerminalApp::implementation
         Microsoft::Terminal::Settings::Model::IActionMapView _actionMap{ nullptr };
         winrt::hstring _keyChord{};
 
+        winrt::Microsoft::Terminal::Settings::Model::ThemeColor _themeColor{ nullptr };
+        winrt::Microsoft::Terminal::Settings::Model::ThemeColor _unfocusedThemeColor{ nullptr };
+        til::color _tabRowColor;
+
         virtual void _CreateContextMenu();
         virtual winrt::hstring _CreateToolTipTitle();
 
@@ -63,6 +72,12 @@ namespace winrt::TerminalApp::implementation
         winrt::fire_and_forget _UpdateSwitchToTabKeyChord();
         void _UpdateToolTip();
 
+        void _RecalculateAndApplyTabColor();
+        void _ApplyTabColorOnUIThread(const winrt::Windows::UI::Color& color);
+        void _ClearTabBackgroundColor();
+        void _RefreshVisualState();
+        virtual winrt::Windows::UI::Xaml::Media::Brush _BackgroundBrush() = 0;
+
         friend class ::TerminalAppLocalTests::TabTests;
     };
 }
diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp
index 36937f4481d..37436c48cc3 100644
--- a/src/cascadia/TerminalApp/TerminalPage.cpp
+++ b/src/cascadia/TerminalApp/TerminalPage.cpp
@@ -19,6 +19,7 @@
 #include "DebugTapConnection.h"
 #include "SettingsTab.h"
 #include "TabRowControl.h"
+#include "Utils.h"
 
 using namespace winrt;
 using namespace winrt::Microsoft::Terminal::Control;
@@ -3321,7 +3322,7 @@ namespace winrt::TerminalApp::implementation
                 }
             });
 
-            auto newTabImpl = winrt::make_self<SettingsTab>(sui);
+            auto newTabImpl = winrt::make_self<SettingsTab>(sui, _settings.GlobalSettings().CurrentTheme().RequestedTheme());
 
             // Add the new tab to the list of our tabs.
             _tabs.Append(*newTabImpl);
@@ -4101,79 +4102,11 @@ namespace winrt::TerminalApp::implementation
         const auto theme = _settings.GlobalSettings().CurrentTheme();
         auto requestedTheme{ theme.RequestedTheme() };
 
-        // First: Update the colors of our individual TabViewItems. This applies tab.background to the tabs via TerminalTab::ThemeColor
-        {
-            auto tabBackground = theme.Tab() ? theme.Tab().Background() : nullptr;
-            for (const auto& tab : _tabs)
-            {
-                if (const auto& terminalTabImpl{ _GetTerminalTabImpl(tab) })
-                {
-                    terminalTabImpl->ThemeColor(tabBackground);
-                }
-            }
-        }
-
         const auto res = Application::Current().Resources();
 
-        // XAML Hacks:
-        //
-        // the App is always in the OS theme, so the
-        // App::Current().Resources() lookup will always get the value for the
-        // OS theme, not the requested theme.
-        //
-        // This helper allows us to instead lookup the value of a resource
-        // specified by `key` for the given `requestedTheme`, from the
-        // dictionaries in App.xaml. Make sure the value is actually there!
-        // Otherwise this'll throw like any other Lookup for a resource that
-        // isn't there.
-        static const auto lookup = [](auto& res, auto& requestedTheme, auto& key) {
-            // You want the Default version of the resource? Great, the App is
-            // always in the OS theme. Just look it up and be done.
-            if (requestedTheme == ElementTheme::Default)
-            {
-                return res.Lookup(key);
-            }
-            static const auto lightKey = winrt::box_value(L"Light");
-            static const auto darkKey = winrt::box_value(L"Dark");
-            // There isn't an ElementTheme::HighContrast.
-
-            auto requestedThemeKey = requestedTheme == ElementTheme::Dark ? darkKey : lightKey;
-            for (const auto& dictionary : res.MergedDictionaries())
-            {
-                // Don't look in the MUX resources. They come first. A person
-                // with more patience than me may find a way to look through our
-                // dictionaries first, then the MUX ones, but that's not needed
-                // currently
-                if (dictionary.Source())
-                {
-                    continue;
-                }
-                // Look through the theme dictionaries we defined:
-                for (const auto& [dictionaryKey, dict] : dictionary.ThemeDictionaries())
-                {
-                    // Does the key for this dict match the theme we're looking for?
-                    if (winrt::unbox_value<winrt::hstring>(dictionaryKey) !=
-                        winrt::unbox_value<winrt::hstring>(requestedThemeKey))
-                    {
-                        // No? skip it.
-                        continue;
-                    }
-                    // Look for the requested resource in this dict.
-                    const auto themeDictionary = dict.as<winrt::Windows::UI::Xaml::ResourceDictionary>();
-                    if (themeDictionary.HasKey(key))
-                    {
-                        return themeDictionary.Lookup(key);
-                    }
-                }
-            }
-
-            // We didn't find it in the requested dict, fall back to the default dictionary.
-            return res.Lookup(key);
-        };
-
         // Use our helper to lookup the theme-aware version of the resource.
         const auto tabViewBackgroundKey = winrt::box_value(L"TabViewBackground");
-        const auto backgroundSolidBrush = lookup(res, requestedTheme, tabViewBackgroundKey).as<Media::SolidColorBrush>();
+        const auto backgroundSolidBrush = ThemeLookup(res, requestedTheme, tabViewBackgroundKey).as<Media::SolidColorBrush>();
 
         til::color bgColor = backgroundSolidBrush.Color();
 
@@ -4218,6 +4151,21 @@ namespace winrt::TerminalApp::implementation
             _tabRow.Background(TitlebarBrush());
         }
 
+        // Second: Update the colors of our individual TabViewItems. This
+        // applies tab.background to the tabs via TerminalTab::ThemeColor.
+        //
+        // Do this second, so that we already know the bgColor of the titlebar.
+        {
+            const auto tabBackground = theme.Tab() ? theme.Tab().Background() : nullptr;
+            const auto tabUnfocusedBackground = theme.Tab() ? theme.Tab().UnfocusedBackground() : nullptr;
+            for (const auto& tab : _tabs)
+            {
+                winrt::com_ptr<TabBase> tabImpl;
+                tabImpl.copy_from(winrt::get_self<TabBase>(tab));
+                tabImpl->ThemeColor(tabBackground, tabUnfocusedBackground, bgColor);
+            }
+        }
+
         // Update the new tab button to have better contrast with the new color.
         // In theory, it would be convenient to also change these for the
         // inactive tabs as well, but we're leaving that as a follow up.
diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp
index 6ef8c735853..4bac51928bf 100644
--- a/src/cascadia/TerminalApp/TerminalTab.cpp
+++ b/src/cascadia/TerminalApp/TerminalTab.cpp
@@ -1394,169 +1394,6 @@ namespace winrt::TerminalApp::implementation
         _RecalculateAndApplyTabColor();
     }
 
-    void TerminalTab::ThemeColor(const winrt::Microsoft::Terminal::Settings::Model::ThemeColor& color)
-    {
-        _themeColor = color;
-        _RecalculateAndApplyTabColor();
-    }
-
-    // Method Description:
-    // - This function dispatches a function to the UI thread to recalculate
-    //   what this tab's current background color should be. If a color is set,
-    //   it will apply the given color to the tab's background. Otherwise, it
-    //   will clear the tab's background color.
-    // Arguments:
-    // - <none>
-    // Return Value:
-    // - <none>
-    void TerminalTab::_RecalculateAndApplyTabColor()
-    {
-        auto weakThis{ get_weak() };
-
-        TabViewItem().Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [weakThis]() {
-            auto ptrTab = weakThis.get();
-            if (!ptrTab)
-                return;
-
-            auto tab{ ptrTab };
-
-            // GetTabColor will return the color set by the color picker, or the
-            // color specified in the profile. If neither of those were set,
-            // then look to _themeColor to see if there's a value there.
-            // Otherwise, clear our color, falling back to the TabView defaults.
-            auto currentColor = tab->GetTabColor();
-            if (currentColor.has_value())
-            {
-                tab->_ApplyTabColor(currentColor.value());
-            }
-            else if (tab->_themeColor != nullptr)
-            {
-                // One-liner to safely get the active control's brush.
-                Media::Brush terminalBrush{ nullptr };
-                if (const auto& c{ tab->GetActiveTerminalControl() })
-                {
-                    terminalBrush = c.BackgroundBrush();
-                }
-
-                if (const auto themeBrush{ tab->_themeColor.Evaluate(Application::Current().Resources(), terminalBrush, false) })
-                {
-                    // ThemeColor.Evaluate will get us a Brush (because the
-                    // TermControl could have an acrylic BG, for example). Take
-                    // that brush, and get the color out of it. We don't really
-                    // want to have the tab items themselves be acrylic.
-                    tab->_ApplyTabColor(til::color{ ThemeColor::ColorFromBrush(themeBrush) });
-                }
-                else
-                {
-                    tab->_ClearTabBackgroundColor();
-                }
-            }
-            else
-            {
-                tab->_ClearTabBackgroundColor();
-            }
-        });
-    }
-
-    // Method Description:
-    // - Applies the given color to the background of this tab's TabViewItem.
-    // - Sets the tab foreground color depending on the luminance of
-    // the background color
-    // - This method should only be called on the UI thread.
-    // Arguments:
-    // - color: the color the user picked for their tab
-    // Return Value:
-    // - <none>
-    void TerminalTab::_ApplyTabColor(const winrt::Windows::UI::Color& color)
-    {
-        Media::SolidColorBrush selectedTabBrush{};
-        Media::SolidColorBrush deselectedTabBrush{};
-        Media::SolidColorBrush fontBrush{};
-        Media::SolidColorBrush secondaryFontBrush{};
-        Media::SolidColorBrush hoverTabBrush{};
-        Media::SolidColorBrush subtleFillColorSecondaryBrush;
-        Media::SolidColorBrush subtleFillColorTertiaryBrush;
-        // calculate the luminance of the current color and select a font
-        // color based on that
-        // see https://www.w3.org/TR/WCAG20/#relativeluminancedef
-        if (TerminalApp::ColorHelper::IsBrightColor(color))
-        {
-            fontBrush.Color(winrt::Windows::UI::Colors::Black());
-            auto secondaryFontColor = winrt::Windows::UI::Colors::Black();
-            // For alpha value see: https://github.com/microsoft/microsoft-ui-xaml/blob/7a33ad772d77d908aa6b316ec24e6d2eb3ebf571/dev/CommonStyles/Common_themeresources_any.xaml#L269
-            secondaryFontColor.A = 0x9E;
-            secondaryFontBrush.Color(secondaryFontColor);
-            auto subtleFillColorSecondary = winrt::Windows::UI::Colors::Black();
-            subtleFillColorSecondary.A = 0x09;
-            subtleFillColorSecondaryBrush.Color(subtleFillColorSecondary);
-            auto subtleFillColorTertiary = winrt::Windows::UI::Colors::Black();
-            subtleFillColorTertiary.A = 0x06;
-            subtleFillColorTertiaryBrush.Color(subtleFillColorTertiary);
-        }
-        else
-        {
-            fontBrush.Color(winrt::Windows::UI::Colors::White());
-            auto secondaryFontColor = winrt::Windows::UI::Colors::White();
-            // For alpha value see: https://github.com/microsoft/microsoft-ui-xaml/blob/7a33ad772d77d908aa6b316ec24e6d2eb3ebf571/dev/CommonStyles/Common_themeresources_any.xaml#L14
-            secondaryFontColor.A = 0xC5;
-            secondaryFontBrush.Color(secondaryFontColor);
-            auto subtleFillColorSecondary = winrt::Windows::UI::Colors::White();
-            subtleFillColorSecondary.A = 0x0F;
-            subtleFillColorSecondaryBrush.Color(subtleFillColorSecondary);
-            auto subtleFillColorTertiary = winrt::Windows::UI::Colors::White();
-            subtleFillColorTertiary.A = 0x0A;
-            subtleFillColorTertiaryBrush.Color(subtleFillColorTertiary);
-        }
-
-        selectedTabBrush.Color(color);
-
-        // currently if a tab has a custom color, a deselected state is
-        // signified by using the same color with a bit of transparency
-        deselectedTabBrush.Color(color);
-        deselectedTabBrush.Opacity(0.3);
-
-        hoverTabBrush.Color(color);
-        hoverTabBrush.Opacity(0.6);
-
-        // Prior to MUX 2.7, we set TabViewItemHeaderBackground, but now we can
-        // use TabViewItem().Background() for that. HOWEVER,
-        // TabViewItem().Background() only sets the color of the tab background
-        // when the TabViewItem is unselected. So we still need to set the other
-        // properties ourselves.
-        //
-        // In GH#11294 we thought we'd still need to set
-        // TabViewItemHeaderBackground manually, but GH#11382 discovered that
-        // Background() was actually okay after all.
-        TabViewItem().Background(deselectedTabBrush);
-        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundSelected"), selectedTabBrush);
-        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPointerOver"), hoverTabBrush);
-        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPressed"), selectedTabBrush);
-
-        // TabViewItem().Foreground() unfortunately does not work for us. It
-        // sets the color for the text when the TabViewItem isn't selected, but
-        // not when it is hovered, pressed, dragged, or selected, so we'll need
-        // to just set them all anyways.
-        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderForeground"), fontBrush);
-        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundSelected"), fontBrush);
-        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundPointerOver"), fontBrush);
-        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundPressed"), fontBrush);
-        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonForeground"), fontBrush);
-        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonForegroundPressed"), secondaryFontBrush);
-        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonForegroundPointerOver"), fontBrush);
-        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderPressedCloseButtonForeground"), fontBrush);
-        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderPointerOverCloseButtonForeground"), fontBrush);
-        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderSelectedCloseButtonForeground"), fontBrush);
-        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonBackgroundPressed"), subtleFillColorTertiaryBrush);
-        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderCloseButtonBackgroundPointerOver"), subtleFillColorSecondaryBrush);
-        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewButtonForegroundActiveTab"), fontBrush);
-        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewButtonForegroundPressed"), fontBrush);
-        TabViewItem().Resources().Insert(winrt::box_value(L"TabViewButtonForegroundPointerOver"), fontBrush);
-
-        _RefreshVisualState();
-
-        _ColorSelectedHandlers(color);
-    }
-
     // Method Description:
     // - Clear the custom runtime color of the tab, if any color is set. This
     //   will re-apply whatever the tab's base color should be (either the color
@@ -1571,54 +1408,14 @@ namespace winrt::TerminalApp::implementation
         _RecalculateAndApplyTabColor();
     }
 
-    // Method Description:
-    // - Clear out any color we've set for the TabViewItem.
-    // - This method should only be called on the UI thread.
-    // Arguments:
-    // - <none>
-    // Return Value:
-    // - <none>
-    void TerminalTab::_ClearTabBackgroundColor()
+    winrt::Windows::UI::Xaml::Media::Brush TerminalTab::_BackgroundBrush()
     {
-        winrt::hstring keys[] = {
-            L"TabViewItemHeaderBackground",
-            L"TabViewItemHeaderBackgroundSelected",
-            L"TabViewItemHeaderBackgroundPointerOver",
-            L"TabViewItemHeaderBackgroundPressed",
-            L"TabViewItemHeaderForeground",
-            L"TabViewItemHeaderForegroundSelected",
-            L"TabViewItemHeaderForegroundPointerOver",
-            L"TabViewItemHeaderForegroundPressed",
-            L"TabViewItemHeaderCloseButtonForeground",
-            L"TabViewItemHeaderCloseButtonForegroundPressed",
-            L"TabViewItemHeaderCloseButtonForegroundPointerOver",
-            L"TabViewItemHeaderPressedCloseButtonForeground",
-            L"TabViewItemHeaderPointerOverCloseButtonForeground",
-            L"TabViewItemHeaderSelectedCloseButtonForeground",
-            L"TabViewItemHeaderCloseButtonBackgroundPressed",
-            L"TabViewItemHeaderCloseButtonBackgroundPointerOver",
-            L"TabViewButtonForegroundActiveTab",
-            L"TabViewButtonForegroundPressed",
-            L"TabViewButtonForegroundPointerOver"
-        };
-
-        // simply clear any of the colors in the tab's dict
-        for (auto keyString : keys)
+        Media::Brush terminalBrush{ nullptr };
+        if (const auto& c{ GetActiveTerminalControl() })
         {
-            auto key = winrt::box_value(keyString);
-            if (TabViewItem().Resources().HasKey(key))
-            {
-                TabViewItem().Resources().Remove(key);
-            }
+            terminalBrush = c.BackgroundBrush();
         }
-
-        // GH#11382 DON'T set the background to null. If you do that, then the
-        // tab won't be hit testable at all. Transparent, however, is a totally
-        // valid hit test target. That makes sense.
-        TabViewItem().Background(WUX::Media::SolidColorBrush{ Windows::UI::Colors::Transparent() });
-
-        _RefreshVisualState();
-        _ColorClearedHandlers();
+        return terminalBrush;
     }
 
     // Method Description:
@@ -1633,27 +1430,6 @@ namespace winrt::TerminalApp::implementation
         _ColorPickerRequestedHandlers();
     }
 
-    // Method Description:
-    // Toggles the visual state of the tab view item,
-    // so that changes to the tab color are reflected immediately
-    // Arguments:
-    // - <none>
-    // Return Value:
-    // - <none>
-    void TerminalTab::_RefreshVisualState()
-    {
-        if (TabViewItem().IsSelected())
-        {
-            VisualStateManager::GoToState(TabViewItem(), L"Normal", true);
-            VisualStateManager::GoToState(TabViewItem(), L"Selected", true);
-        }
-        else
-        {
-            VisualStateManager::GoToState(TabViewItem(), L"Selected", true);
-            VisualStateManager::GoToState(TabViewItem(), L"Normal", true);
-        }
-    }
-
     // - Get the total number of leaf panes in this tab. This will be the number
     //   of actual controls hosted by this tab.
     // Arguments:
diff --git a/src/cascadia/TerminalApp/TerminalTab.h b/src/cascadia/TerminalApp/TerminalTab.h
index 5f7a4c8ed82..53492095db1 100644
--- a/src/cascadia/TerminalApp/TerminalTab.h
+++ b/src/cascadia/TerminalApp/TerminalTab.h
@@ -71,9 +71,7 @@ namespace winrt::TerminalApp::implementation
         void ResetTabText();
         void ActivateTabRenamer();
 
-        std::optional<winrt::Windows::UI::Color> GetTabColor();
-
-        void ThemeColor(const winrt::Microsoft::Terminal::Settings::Model::ThemeColor& color);
+        virtual std::optional<winrt::Windows::UI::Color> GetTabColor() override;
         void SetRuntimeTabColor(const winrt::Windows::UI::Color& color);
         void ResetRuntimeTabColor();
         void RequestColorPicker();
@@ -100,8 +98,6 @@ namespace winrt::TerminalApp::implementation
         }
 
         WINRT_CALLBACK(ActivePaneChanged, winrt::delegate<>);
-        WINRT_CALLBACK(ColorSelected, winrt::delegate<winrt::Windows::UI::Color>);
-        WINRT_CALLBACK(ColorCleared, winrt::delegate<>);
         WINRT_CALLBACK(TabRaiseVisualBell, winrt::delegate<>);
         WINRT_CALLBACK(DuplicateRequested, winrt::delegate<>);
         WINRT_CALLBACK(SplitTabRequested, winrt::delegate<>);
@@ -119,7 +115,6 @@ namespace winrt::TerminalApp::implementation
         std::optional<winrt::Windows::UI::Color> _runtimeTabColor{};
         winrt::TerminalApp::TabHeaderControl _headerControl{};
         winrt::TerminalApp::TerminalTabStatus _tabStatus{};
-        winrt::Microsoft::Terminal::Settings::Model::ThemeColor _themeColor{ nullptr };
 
         winrt::TerminalApp::ColorPickupFlyout _tabColorPickup{ nullptr };
         winrt::event_token _colorSelectedToken;
@@ -163,8 +158,6 @@ namespace winrt::TerminalApp::implementation
         void _CreateContextMenu() override;
         virtual winrt::hstring _CreateToolTipTitle() override;
 
-        void _RefreshVisualState();
-
         void _DetachEventHandlersFromControl(const uint32_t paneId, const winrt::Microsoft::Terminal::Control::TermControl& control);
         void _AttachEventHandlersToControl(const uint32_t paneId, const winrt::Microsoft::Terminal::Control::TermControl& control);
         void _AttachEventHandlersToPane(std::shared_ptr<Pane> pane);
@@ -173,16 +166,14 @@ namespace winrt::TerminalApp::implementation
 
         winrt::hstring _GetActiveTitle() const;
 
-        void _RecalculateAndApplyTabColor();
-        void _ApplyTabColor(const winrt::Windows::UI::Color& color);
-        void _ClearTabBackgroundColor();
-
         void _RecalculateAndApplyReadOnly();
 
         void _UpdateProgressState();
 
         void _DuplicateTab();
 
+        virtual winrt::Windows::UI::Xaml::Media::Brush _BackgroundBrush() override;
+
         friend class ::TerminalAppLocalTests::TabTests;
     };
 }
diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h
index 4ebc362ddd6..502b4adb404 100644
--- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h
+++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h
@@ -129,6 +129,7 @@ Author(s):
     X(winrt::Microsoft::Terminal::Settings::Model::ThemeColor, Background, "background", nullptr) \
     X(winrt::Microsoft::Terminal::Settings::Model::ThemeColor, UnfocusedBackground, "unfocusedBackground", nullptr)
 
-#define MTSM_THEME_TAB_SETTINGS(X)                                                                \
-    X(winrt::Microsoft::Terminal::Settings::Model::ThemeColor, Background, "background", nullptr) \
+#define MTSM_THEME_TAB_SETTINGS(X)                                                                                  \
+    X(winrt::Microsoft::Terminal::Settings::Model::ThemeColor, Background, "background", nullptr)                   \
+    X(winrt::Microsoft::Terminal::Settings::Model::ThemeColor, UnfocusedBackground, "unfocusedBackground", nullptr) \
     X(winrt::Microsoft::Terminal::Settings::Model::TabCloseButtonVisibility, ShowCloseButton, "showCloseButton", winrt::Microsoft::Terminal::Settings::Model::TabCloseButtonVisibility::Always)
diff --git a/src/cascadia/TerminalSettingsModel/Theme.cpp b/src/cascadia/TerminalSettingsModel/Theme.cpp
index dfb108634ac..c7236a8463f 100644
--- a/src/cascadia/TerminalSettingsModel/Theme.cpp
+++ b/src/cascadia/TerminalSettingsModel/Theme.cpp
@@ -199,6 +199,30 @@ winrt::WUX::Media::Brush ThemeColor::Evaluate(const winrt::WUX::ResourceDictiona
     return nullptr;
 }
 
+// Method Description:
+// - This is not an actual property on a theme color setting, but rather
+//   something derived from the value itself. This is "the opacity we should use
+//   for this ThemeColor should it be used as a unfocusedTab color". Basically,
+//   terminalBackground and accent use 30% opacity when set, to match the how
+//   inactive tabs were colored before themes existed.
+// Arguments:
+// - <none>
+// Return Value:
+// - the opacity that should be used if this color is being applied to a
+//   tab.unfocusedBackground property.
+uint8_t ThemeColor::UnfocusedTabOpacity() const noexcept
+{
+    switch (ColorType())
+    {
+    case ThemeColorType::Accent:
+    case ThemeColorType::TerminalBackground:
+        return 77; // 77 = .3 * 256
+    case ThemeColorType::Color:
+        return _Color.a;
+    }
+    return 0;
+}
+
 #define THEME_SETTINGS_FROM_JSON(type, name, jsonKey, ...)                    \
     {                                                                         \
         std::optional<type> _val;                                             \
diff --git a/src/cascadia/TerminalSettingsModel/Theme.h b/src/cascadia/TerminalSettingsModel/Theme.h
index 96a496b908c..e39d42b8f20 100644
--- a/src/cascadia/TerminalSettingsModel/Theme.h
+++ b/src/cascadia/TerminalSettingsModel/Theme.h
@@ -39,6 +39,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
         winrt::Windows::UI::Xaml::Media::Brush Evaluate(const winrt::Windows::UI::Xaml::ResourceDictionary& res,
                                                         const winrt::Windows::UI::Xaml::Media::Brush& terminalBackground,
                                                         const bool forTitlebar);
+        uint8_t UnfocusedTabOpacity() const noexcept;
 
         WINRT_PROPERTY(til::color, Color);
         WINRT_PROPERTY(winrt::Microsoft::Terminal::Settings::Model::ThemeColorType, ColorType);
diff --git a/src/cascadia/TerminalSettingsModel/Theme.idl b/src/cascadia/TerminalSettingsModel/Theme.idl
index 32127abf2ce..39260bb0cf4 100644
--- a/src/cascadia/TerminalSettingsModel/Theme.idl
+++ b/src/cascadia/TerminalSettingsModel/Theme.idl
@@ -32,6 +32,7 @@ namespace Microsoft.Terminal.Settings.Model
         Windows.UI.Xaml.Media.Brush Evaluate(Windows.UI.Xaml.ResourceDictionary res,
                                              Windows.UI.Xaml.Media.Brush terminalBackground,
                                              Boolean forTitlebar);
+        UInt8 UnfocusedTabOpacity { get; };
     }
 
     runtimeclass WindowTheme {
@@ -45,6 +46,7 @@ namespace Microsoft.Terminal.Settings.Model
 
     runtimeclass TabTheme {
         ThemeColor Background { get; };
+        ThemeColor UnfocusedBackground { get; };
         TabCloseButtonVisibility ShowCloseButton { get; };
     }
 
diff --git a/src/cascadia/WinRTUtils/inc/Utils.h b/src/cascadia/WinRTUtils/inc/Utils.h
index 82824c5bfaa..ec8233fcb3c 100644
--- a/src/cascadia/WinRTUtils/inc/Utils.h
+++ b/src/cascadia/WinRTUtils/inc/Utils.h
@@ -53,3 +53,66 @@ winrt::Windows::Foundation::IAsyncOperation<winrt::hstring> SaveFilePicker(HWND
 }
 
 winrt::Windows::Foundation::IAsyncOperation<winrt::hstring> OpenImagePicker(HWND parentHwnd);
+
+#ifdef WINRT_Windows_UI_Xaml_H
+// Only compile me if Windows.UI.Xaml is already included.
+//
+// XAML Hacks:
+//
+// the App is always in the OS theme, so the
+// App::Current().Resources() lookup will always get the value for the
+// OS theme, not the requested theme.
+//
+// This helper allows us to instead lookup the value of a resource
+// specified by `key` for the given `requestedTheme`, from the
+// dictionaries in App.xaml. Make sure the value is actually there!
+// Otherwise this'll throw like any other Lookup for a resource that
+// isn't there.
+winrt::Windows::Foundation::IInspectable ThemeLookup(const auto& res,
+                                                     const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme,
+                                                     const winrt::Windows::Foundation::IInspectable& key)
+{
+    // You want the Default version of the resource? Great, the App is
+    // always in the OS theme. Just look it up and be done.
+    if (requestedTheme == winrt::Windows::UI::Xaml::ElementTheme::Default)
+    {
+        return res.Lookup(key);
+    }
+    static const auto lightKey = winrt::box_value(L"Light");
+    static const auto darkKey = winrt::box_value(L"Dark");
+    // There isn't an ElementTheme::HighContrast.
+
+    const auto requestedThemeKey = requestedTheme == winrt::Windows::UI::Xaml::ElementTheme::Dark ? darkKey : lightKey;
+    for (const auto& dictionary : res.MergedDictionaries())
+    {
+        // Don't look in the MUX resources. They come first. A person
+        // with more patience than me may find a way to look through our
+        // dictionaries first, then the MUX ones, but that's not needed
+        // currently
+        if (dictionary.Source())
+        {
+            continue;
+        }
+        // Look through the theme dictionaries we defined:
+        for (const auto& [dictionaryKey, dict] : dictionary.ThemeDictionaries())
+        {
+            // Does the key for this dict match the theme we're looking for?
+            if (winrt::unbox_value<winrt::hstring>(dictionaryKey) !=
+                winrt::unbox_value<winrt::hstring>(requestedThemeKey))
+            {
+                // No? skip it.
+                continue;
+            }
+            // Look for the requested resource in this dict.
+            const auto themeDictionary = dict.as<winrt::Windows::UI::Xaml::ResourceDictionary>();
+            if (themeDictionary.HasKey(key))
+            {
+                return themeDictionary.Lookup(key);
+            }
+        }
+    }
+
+    // We didn't find it in the requested dict, fall back to the default dictionary.
+    return res.Lookup(key);
+};
+#endif
diff --git a/src/inc/til/color.h b/src/inc/til/color.h
index 5054be53318..ca07522b35b 100644
--- a/src/inc/til/color.h
+++ b/src/inc/til/color.h
@@ -134,6 +134,29 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
             };
         }
 
+        // source-over alpha blending/composition.
+        // `this` (source/top) will be blended "over" `destination` (bottom).
+        // `this` and `destination` are expected to be in straight alpha.
+        // See https://en.wikipedia.org/wiki/Alpha_compositing#Description
+        constexpr color layer_over(const color& destination) const
+        {
+            const auto sourceAlpha = a / 255.0f;
+            const auto destinationAlpha = destination.a / 255.0f;
+            const auto aInverse = 1.0f - sourceAlpha;
+
+            const auto resultA = a + destination.a * aInverse;
+            const auto resultR = (r * sourceAlpha + destination.r * destinationAlpha * aInverse) / resultA;
+            const auto resultG = (g * sourceAlpha + destination.g * destinationAlpha * aInverse) / resultA;
+            const auto resultB = (b * sourceAlpha + destination.b * destinationAlpha * aInverse) / resultA;
+
+            return {
+                static_cast<uint8_t>(resultR + 0.5f),
+                static_cast<uint8_t>(resultG + 0.5f),
+                static_cast<uint8_t>(resultB + 0.5f),
+                static_cast<uint8_t>(resultA + 0.5f),
+            };
+        }
+
 #ifdef D3DCOLORVALUE_DEFINED
         constexpr operator D3DCOLORVALUE() const
         {


Test patch: