' Customisable dark mode for Excel cells
' https://github.com/stu-bell/ExcelDarkMode
' Copyright (c) 2021 Stuart Bell
' Licenced under the MIT licence: https://github.com/stu-bell/ExcelDarkMode/blob/main/LICENSE
' Only modifies cell styles. Will not change colors of cells that have been formatted separately.
' To include custom formatted cells in dark mode, create a new style for that formatting and include the style in this module
' Color codes for each style must be inserted into the code below for both DarkMode and LightMode (see comments in Functions below)
' Original table styles are not preserved when switching back to light mode - you'll need to specify the default light style in code or use sub SetWorkbookTableStyle
' Save this macro in your PERSONAL.XLSB (and add it to your quick access bar!) so you can use dark mode in any new workbook, including non-macro enabled ones
Function DarkMode()
' Set all tables to this dark table style
Call SetAllTableStyle("TableStyleDark1")
' Dark colors for each style
Call UpdateStyleColors(styleName:="Normal", fillColorHex:="#2E3440", fontColorHex:="#FFFFFF", borderColorHex:="#454545")
Call UpdateStyleColors(styleName:="Heading 1", fillColorHex:="#2E3440", fontColorHex:="#FFFFFF")
Call UpdateStyleColors(styleName:="Heading 2", fillColorHex:="#2E3440", fontColorHex:="#FFFFFF")
Call UpdateStyleColors(styleName:="Heading 3", fillColorHex:="#2E3440", fontColorHex:="#FFFFFF")
Call UpdateStyleColors(styleName:="Heading 4", fillColorHex:="#2E3440", fontColorHex:="#FFFFFF")
Call UpdateStyleColors(styleName:="Title", fillColorHex:="#2E3440", fontColorHex:="#FFFFFF")
Call UpdateStyleColors(styleName:="Total", fillColorHex:="#2E3440", fontColorHex:="#FFFFFF")
Call UpdateStyleColors(styleName:="Note", fillColorHex:="#B2B2B2", fontColorHex:="#000000", borderColorHex:="#454545")
Call UpdateStyleColors(styleName:="Explanatory Text", fillColorHex:="#2E3440", fontColorHex:="#FFFFFF", borderColorHex:="#454545")
End Function
Function LightMode()
' Set all tables to this light table style
Call SetAllTableStyle("TableStyleMedium9")
' Light colors for each style
Call UpdateStyleColors(styleName:="Normal", fillColorHex:="#FFFFFF", fontColorHex:="#000000", borderLineStyle:=xlNone, interiorPattern:=xlNone)
Call UpdateStyleColors(styleName:="Heading 1", fontColorHex:="#44546A", interiorPattern:=xlNone)
Call UpdateStyleColors(styleName:="Heading 2", fontColorHex:="#44546A", interiorPattern:=xlNone)
Call UpdateStyleColors(styleName:="Heading 3", fontColorHex:="#44546A", interiorPattern:=xlNone)
Call UpdateStyleColors(styleName:="Heading 4", fontColorHex:="#44546A", interiorPattern:=xlNone)
Call UpdateStyleColors(styleName:="Title", fontColorHex:="#44546A", interiorPattern:=xlNone)
Call UpdateStyleColors(styleName:="Total", fontColorHex:="#000000", interiorPattern:=xlNone)
Call UpdateStyleColors(styleName:="Note", fillColorHex:="#FFFFCC", fontColorHex:="#000000", borderColorHex:="#B2B2B2")
Call UpdateStyleColors(styleName:="Explanatory Text", fontColorHex:="#7F7F7F", borderColorHex:="#454545", interiorPattern:=xlNone)
End Function
Function DarkModeWithBackup()
' Set all tables to this dark table style
Call SetAllTableStyle("TableStyleDark1")
' List calls to dark styles
Call ApplyDarkStyle(styleName:="Normal", fillColorHex:="#2E3440", fontColorHex:="#FFFFFF", borderColorHex:="#454545")
Call ApplyDarkStyle(styleName:="Heading 1", fillColorHex:="#2E3440", fontColorHex:="#FFFFFF")
Call ApplyDarkStyle(styleName:="Heading 2", fillColorHex:="#2E3440", fontColorHex:="#FFFFFF")
Call ApplyDarkStyle(styleName:="Heading 3", fillColorHex:="#2E3440", fontColorHex:="#FFFFFF")
Call ApplyDarkStyle(styleName:="Heading 4", fillColorHex:="#2E3440", fontColorHex:="#FFFFFF")
Call ApplyDarkStyle(styleName:="Title", fillColorHex:="#2E3440", fontColorHex:="#FFFFFF")
Call ApplyDarkStyle(styleName:="Total", fillColorHex:="#2E3440", fontColorHex:="#FFFFFF")
Call ApplyDarkStyle(styleName:="Note", fillColorHex:="#B2B2B2", fontColorHex:="#000000", borderColorHex:="#454545")
Call ApplyDarkStyle(styleName:="Explanatory Text", fillColorHex:="#2E3440", fontColorHex:="#FFFFFF", borderColorHex:="#454545")
' This should fail without error, as the style doesn't exist
'Call ApplyDarkStyle(styleName:="noexistlsakjalsdfkj", fillColorHex:="#000000")
End Function
Function LightModeFromBackup()
' Set all tables to this light table style
Call SetAllTableStyle("TableStyleLight1")
' List calls to light style FIXME: loop this
Call RestoreLightStyle("Normal")
Call RestoreLightStyle("Heading 1")
Call RestoreLightStyle("Heading 2")
Call RestoreLightStyle("Heading 3")
Call RestoreLightStyle("Heading 4")
Call RestoreLightStyle("Title")
Call RestoreLightStyle("Total")
Call RestoreLightStyle("Note")
Call RestoreLightStyle("Explanatory Text")
End Function
Sub ToggleDarkMode()
Application.ScreenUpdating = False
' Create a custom property to save state of Dark/Light mode in the workbook
Dim flag As String
flag = "DARK_MODE_0292"
If Not CustomPropertyExists(flag) Then
ActiveWorkbook.CustomDocumentProperties.Add Name:=flag, Value:=0, _
LinkToContent:=False, _
Type:=msoPropertyTypeNumber
End If
' Toggle state based on flag
If ActiveWorkbook.CustomDocumentProperties(flag).Value = 1 Then
' Dark to Light
ActiveWorkbook.CustomDocumentProperties(flag).Value = 0
Call LightMode
Else
' Light to Dark
ActiveWorkbook.CustomDocumentProperties(flag).Value = 1
Call DarkMode
End If
Application.ScreenUpdating = True
End Sub
'Resets all tables to the style named here
Sub SetWorkbookTableStyle()
Dim tabStyleName As String
tabStyleName = InputBox("This table style will be applied to all tables in the workbook." & vbCrLf & "Available table style names can be found in the Table Design ribbon (just remove spaces from the name in this box)", "Table Style Name", "TableStyleLight1")
Call SetAllTableStyle(tabStyleName)
End Sub
' Resets formatting of cells to their original style (resets all formatting done on top of ANY style)
' If the workbook hasn't had styles properly used you'll loose a lot of formatting
' Use with caution!
Sub ResetStyles()
' https://jkp-ads.com/Articles/styles06.asp
Dim oCell As Range
Dim oSh As Worksheet
If MsgBox("This will erase all additional formatting on top of the existing cell styles in the selected sheets." & vbNewLine & _
"If you're not sure, Cancel this and save a copy of your workbook", _
vbCritical + vbOKCancel + vbDefaultButton2, "This step is not reversible") = vbOK Then
Application.ScreenUpdating = False
For Each oSh In ActiveWindow.SelectedSheets
For Each oCell In oSh.UsedRange.Cells
If oCell.MergeArea.Cells.Count = 1 Then
' reapply original style and remove additional formatting
oCell.Style = CStr(oCell.Style)
End If
Next
Next
End If
Application.ScreenUpdating = True
End Sub
' Change the color properties of the style
' To modify a new property (eg font name) set the property as a new optional arg
' All style params must be optional and tested for with `If Not IsMissing(paramName)`
Function UpdateStyleColors(styleName As String, _
Optional fillColorHex As String, _
Optional fontColorHex As String, _
Optional borderColorHex As String, _
Optional borderLineStyle As XlLineStyle, _
Optional interiorPattern As XlPattern)
' Skip styles we haven't defined
On Error Resume Next
' Make sure the style includes all of the elements we want to change (eg Heading 1 doesn't include Patterns by default
With ActiveWorkbook.Styles(styleName)
.IncludeFont = True
.IncludeBorder = True
.IncludePatterns = True
End With
' Set the properties of the target style (only if a parameter has been passed for that property)
' FIXME can we choose properties dynamically in VBA?
With ActiveWorkbook.Styles(styleName)
If Not IsMissing(fillColorHex) Then
.Interior.Color = HexToRGB(fillColorHex)
End If
If Not IsMissing(fontColorHex) Then
.Font.Color = HexToRGB(fontColorHex)
End If
If Not IsMissing(borderColorHex) Then
.Borders(xlLeft).Color = HexToRGB(borderColorHex)
.Borders(xlRight).Color = HexToRGB(borderColorHex)
.Borders(xlBottom).Color = HexToRGB(borderColorHex)
.Borders(xlTop).Color = HexToRGB(borderColorHex)
End If
If borderLineStyle <> 0 Then
.Borders(xlLeft).LineStyle = borderLineStyle
.Borders(xlRight).LineStyle = borderLineStyle
.Borders(xlBottom).LineStyle = borderLineStyle
.Borders(xlTop).LineStyle = borderLineStyle
End If
If interiorPattern <> 0 Then
.Interior.Pattern = interiorPattern
End If
End With
End Function
' Change the color properties of the style to make them dark. Stores original style colors in a backup style
' To modify a new property (eg font name) set the property as a new optional arg and make sure to add the property definition to backup style (this function), the actual style (this function) and the function RestoreLightStyle
' All style params must be optional and tested for with `If Not IsMissing(paramName)`
Function ApplyDarkStyle(styleName As String, _
Optional fillColorHex As String, _
Optional fontColorHex As String, _
Optional borderColorHex As String)
' Skip styles we haven't defined
On Error Resume Next
' Make sure the style includes all of the elements we want to change (eg Heading 1 doesn't include Patterns by default
With ActiveWorkbook.Styles(styleName)
.IncludeFont = True
.IncludeBorder = True
.IncludePatterns = True
End With
' Create a backup style for the style, saving the original
With ActiveWorkbook.Styles.Add(BackupStyleName(styleName))
If Not IsMissing(fillColorHex) Then
.Interior.Color = ActiveWorkbook.Styles(styleName).Interior.Color
End If
If Not IsMissing(fontColorHex) Then
.Font.Color = ActiveWorkbook.Styles(styleName).Font.Color
End If
If Not IsMissing(borderColorHex) Then
.Borders(xlLeft).Color = ActiveWorkbook.Styles(styleName).Borders(xlLeft).Color
.Borders(xlRight).Color = ActiveWorkbook.Styles(styleName).Borders(xlRight).Color
.Borders(xlBottom).Color = ActiveWorkbook.Styles(styleName).Borders(xlBottom).Color
.Borders(xlTop).Color = ActiveWorkbook.Styles(styleName).Borders(xlTop).Color
.Borders(xlLeft).LineStyle = ActiveWorkbook.Styles(styleName).Borders(xlLeft).LineStyle
.Borders(xlRight).LineStyle = ActiveWorkbook.Styles(styleName).Borders(xlRight).LineStyle
.Borders(xlBottom).LineStyle = ActiveWorkbook.Styles(styleName).Borders(xlBottom).LineStyle
.Borders(xlTop).LineStyle = ActiveWorkbook.Styles(styleName).Borders(xlTop).LineStyle
End If
End With
' Backup the interior pattern
ActiveWorkbook.Styles(BackupStyleName(styleName)).Interior.Pattern = ActiveWorkbook.Styles(styleName).Interior.Pattern
' Set the properties of the target style (only if a parameter has been passed for that property)
' FIXME can we choose properties dynamically in VBA?
With ActiveWorkbook.Styles(styleName)
If Not IsMissing(fillColorHex) Then
.Interior.Color = HexToRGB(fillColorHex)
End If
If Not IsMissing(fontColorHex) Then
.Font.Color = HexToRGB(fontColorHex)
End If
If Not IsMissing(borderColorHex) Then
.Borders(xlLeft).Color = HexToRGB(borderColorHex)
.Borders(xlRight).Color = HexToRGB(borderColorHex)
.Borders(xlBottom).Color = HexToRGB(borderColorHex)
.Borders(xlTop).Color = HexToRGB(borderColorHex)
End If
End With
End Function
' Reset each property to the light style from the backup
Function RestoreLightStyle(styleName As String)
' Skip styles we haven't defined
On Error Resume Next
With ActiveWorkbook.Styles(styleName)
.Interior.Color = ActiveWorkbook.Styles(BackupStyleName(styleName)).Interior.Color
.Font.Color = ActiveWorkbook.Styles(BackupStyleName(styleName)).Font.Color
.Borders(xlLeft).Color = ActiveWorkbook.Styles(BackupStyleName(styleName)).Borders(xlLeft).Color
.Borders(xlRight).Color = ActiveWorkbook.Styles(BackupStyleName(styleName)).Borders(xlRight).Color
.Borders(xlBottom).Color = ActiveWorkbook.Styles(BackupStyleName(styleName)).Borders(xlBottom).Color
.Borders(xlTop).Color = ActiveWorkbook.Styles(BackupStyleName(styleName)).Borders(xlTop).Color
.Borders(xlLeft).LineStyle = ActiveWorkbook.Styles(BackupStyleName(styleName)).Borders(xlLeft).LineStyle
.Borders(xlRight).LineStyle = ActiveWorkbook.Styles(BackupStyleName(styleName)).Borders(xlRight).LineStyle
.Borders(xlBottom).LineStyle = ActiveWorkbook.Styles(BackupStyleName(styleName)).Borders(xlBottom).LineStyle
.Borders(xlTop).LineStyle = ActiveWorkbook.Styles(BackupStyleName(styleName)).Borders(xlTop).LineStyle
.Interior.Pattern = ActiveWorkbook.Styles(BackupStyleName(styleName)).Interior.Pattern
End With
' Clean up the backup style
ActiveWorkbook.Styles(BackupStyleName(styleName)).Delete
End Function
' Wrapper to get the backup name for a known style name
Function BackupStyleName(styleName As String) As String
BackupStyleName = styleName & "_DARKMODE_BACKUP"
End Function
' Loops through each table in workbook and applies the named table style. Slow for many tables!
Function SetAllTableStyle(styleName As String)
Dim tbl As ListObject
Dim sht As Worksheet
For Each sht In ActiveWorkbook.Worksheets
For Each tbl In sht.ListObjects
tbl.TableStyle = styleName
Next tbl
Next sht
End Function
'Convert Hex color codes to RGB for setting color properties in VBA
Function HexToRGB(hex As String)
' remove optional leading #
nohash = Replace(hex, "#", "")
' split hex code into rgb parts
red = CLng("&H" & Left(nohash, 2))
green = CLng("&H" & Mid(nohash, 3, 2))
blue = CLng("&H" & Right(nohash, 2))
HexToRGB = RGB(red, green, blue)
End Function
Sub HexToRGB_test()
MsgBox _
RGB(200, 100, 50) = HexToRGB("#C86432") And _
RGB(200, 100, 50) = HexToRGB("C86432") And _
RGB(255, 255, 255) = HexToRGB("FFFFFF") And _
RGB(0, 0, 0) = HexToRGB("000000")
End Sub
Function CustomPropertyExists(propName As String) As Boolean
Dim wb As Workbook
Dim docProp As DocumentProperty
Dim propExists As Boolean
Set wb = Application.ActiveWorkbook
For Each docProp In wb.CustomDocumentProperties
If docProp.Name = propName Then
propExists = True
Exit For
End If
Next
CustomPropertyExists = propExists
End Function