[tint][utils] Add light theme, detect dark / light

Add a new tint `--color` command line flag to explicitly control color
output and theme.

Change-Id: I54bbdf8ecb86dfdd0c74e020b2858a81850a6102
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/176120
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/cmd/common/helper.cc b/src/tint/cmd/common/helper.cc
index 2e9ce37..a1fbaf0 100644
--- a/src/tint/cmd/common/helper.cc
+++ b/src/tint/cmd/common/helper.cc
@@ -269,9 +269,13 @@
             PrintWGSL(std::cout, info.program);
         }
 
-        auto printer = tint::StyledTextPrinter::Create(stderr);
         tint::diag::Formatter formatter;
-        printer->Print(formatter.Format(info.program.Diagnostics()));
+        if (opts.printer) {
+            opts.printer->Print(formatter.Format(info.program.Diagnostics()));
+        } else {
+            tint::StyledTextPrinter::Create(stderr)->Print(
+                formatter.Format(info.program.Diagnostics()));
+        }
     }
 
     if (!info.program.IsValid()) {
diff --git a/src/tint/cmd/common/helper.h b/src/tint/cmd/common/helper.h
index 04e6961..dbc8e38 100644
--- a/src/tint/cmd/common/helper.h
+++ b/src/tint/cmd/common/helper.h
@@ -82,6 +82,8 @@
     bool use_ir = false;
     tint::spirv::reader::Options spirv_reader_options;
 #endif
+    /// The text printer to use for output
+    StyledTextPrinter* printer = nullptr;
 };
 
 /// Loads the source and program information for the given file.
diff --git a/src/tint/cmd/tint/BUILD.bazel b/src/tint/cmd/tint/BUILD.bazel
index d77264b..bdba7b0 100644
--- a/src/tint/cmd/tint/BUILD.bazel
+++ b/src/tint/cmd/tint/BUILD.bazel
@@ -76,6 +76,7 @@
     "//src/tint/utils/rtti",
     "//src/tint/utils/strconv",
     "//src/tint/utils/symbol",
+    "//src/tint/utils/system",
     "//src/tint/utils/text",
     "//src/tint/utils/traits",
   ] + select({
diff --git a/src/tint/cmd/tint/BUILD.cmake b/src/tint/cmd/tint/BUILD.cmake
index f354d49..8ed82d1 100644
--- a/src/tint/cmd/tint/BUILD.cmake
+++ b/src/tint/cmd/tint/BUILD.cmake
@@ -77,6 +77,7 @@
   tint_utils_rtti
   tint_utils_strconv
   tint_utils_symbol
+  tint_utils_system
   tint_utils_text
   tint_utils_traits
 )
diff --git a/src/tint/cmd/tint/BUILD.gn b/src/tint/cmd/tint/BUILD.gn
index e51fbf4..7f3584f 100644
--- a/src/tint/cmd/tint/BUILD.gn
+++ b/src/tint/cmd/tint/BUILD.gn
@@ -76,6 +76,7 @@
     "${tint_src_dir}/utils/rtti",
     "${tint_src_dir}/utils/strconv",
     "${tint_src_dir}/utils/symbol",
+    "${tint_src_dir}/utils/system",
     "${tint_src_dir}/utils/text",
     "${tint_src_dir}/utils/traits",
   ]
diff --git a/src/tint/cmd/tint/main.cc b/src/tint/cmd/tint/main.cc
index f4913a9..5a11673 100644
--- a/src/tint/cmd/tint/main.cc
+++ b/src/tint/cmd/tint/main.cc
@@ -57,6 +57,8 @@
 #include "src/tint/utils/containers/transform.h"
 #include "src/tint/utils/diagnostic/formatter.h"
 #include "src/tint/utils/macros/defer.h"
+#include "src/tint/utils/system/env.h"
+#include "src/tint/utils/system/terminal.h"
 #include "src/tint/utils/text/string.h"
 #include "src/tint/utils/text/string_stream.h"
 #include "src/tint/utils/text/styled_text.h"
@@ -151,9 +153,14 @@
 constexpr uint32_t kMinShaderModelForPackUnpack4x8InHLSL = 66u;
 #endif  // TINT_BUILD_HLSL_WRITER
 
+/// An enumerator of color-output modes
+enum class ColorMode { kPlain, kDark, kLight };
+
 struct Options {
     bool verbose = false;
 
+    std::unique_ptr<tint::StyledTextPrinter> printer;
+
     std::string input_filename;
     std::string output_file = "-";  // Default to stdout
 
@@ -209,9 +216,38 @@
 #endif  // TINT_BUILD_SYNTAX_TREE_WRITER
 };
 
+/// @returns the default ColorMode when no `--color` flag is specified.
+ColorMode ColorModeDefault() {
+    if (!tint::TerminalSupportsColors(stdout)) {
+        return ColorMode::kPlain;
+    }
+    if (auto res = tint::TerminalIsDark(stdout)) {
+        return *res ? ColorMode::kDark : ColorMode::kLight;
+    }
+    if (auto env = tint::GetEnvVar("DARK_BACKGROUND_COLOR"); !env.empty()) {
+        return env != "0" ? ColorMode::kDark : ColorMode::kLight;
+    }
+    if (auto env = tint::GetEnvVar("LIGHT_BACKGROUND_COLOR"); !env.empty()) {
+        return env != "0" ? ColorMode::kLight : ColorMode::kDark;
+    }
+    return ColorMode::kDark;
+}
+
+std::unique_ptr<tint::StyledTextPrinter> CreatePrinter(ColorMode mode) {
+    switch (mode) {
+        case ColorMode::kLight:
+            return tint::StyledTextPrinter::Create(stderr, tint::StyledTextTheme::kDefaultLight);
+        case ColorMode::kDark:
+            return tint::StyledTextPrinter::Create(stderr, tint::StyledTextTheme::kDefaultDark);
+        case ColorMode::kPlain:
+            break;
+    }
+    return tint::StyledTextPrinter::CreatePlain(stderr);
+}
+
 /// @param filename the filename to inspect
 /// @returns the inferred format for the filename suffix
-Format infer_format(const std::string& filename) {
+Format InferFormat(const std::string& filename) {
     (void)filename;
 
 #if TINT_BUILD_SPV_WRITER
@@ -272,6 +308,15 @@
                                                 format_enum_names, ShortName{"f"});
     TINT_DEFER(opts->format = fmt.value.value_or(Format::kUnknown));
 
+    auto& col = options.Add<EnumOption<ColorMode>>("color", "Use colored output",
+                                                   tint::Vector{
+                                                       EnumName{ColorMode::kPlain, "off"},
+                                                       EnumName{ColorMode::kDark, "dark"},
+                                                       EnumName{ColorMode::kLight, "light"},
+                                                   },
+                                                   ShortName{"col"}, Default{ColorModeDefault()});
+    TINT_DEFER(opts->printer = CreatePrinter(*col.value));
+
     auto& ep = options.Add<StringOption>("entry-point", "Output single entry point",
                                          ShortName{"ep"}, Parameter{"name"});
     TINT_DEFER({
@@ -790,9 +835,8 @@
         auto source = std::make_unique<tint::Source::File>(options.input_filename, result->wgsl);
         auto reparsed_program = tint::wgsl::reader::Parse(source.get(), parser_options);
         if (!reparsed_program.IsValid()) {
-            auto printer = tint::StyledTextPrinter::Create(stderr);
             tint::diag::Formatter diag_formatter;
-            printer->Print(diag_formatter.Format(reparsed_program.Diagnostics()));
+            options.printer->Print(diag_formatter.Format(reparsed_program.Diagnostics()));
             return false;
         }
     }
@@ -1255,7 +1299,7 @@
     // Implement output format defaults.
     if (options.format == Format::kUnknown) {
         // Try inferring from filename.
-        options.format = infer_format(options.output_file);
+        options.format = InferFormat(options.output_file);
     }
     if (options.format == Format::kUnknown) {
         // Ultimately, default to SPIR-V assembly. That's nice for interactive use.
@@ -1264,6 +1308,7 @@
 
     tint::cmd::LoadProgramOptions opts;
     opts.filename = options.input_filename;
+    opts.printer = options.printer.get();
 #if TINT_BUILD_SPV_READER
     opts.use_ir = options.use_ir_reader;
     opts.spirv_reader_options = options.spirv_reader_options;
diff --git a/src/tint/utils/BUILD.cmake b/src/tint/utils/BUILD.cmake
index badeb73..1771b07 100644
--- a/src/tint/utils/BUILD.cmake
+++ b/src/tint/utils/BUILD.cmake
@@ -53,5 +53,6 @@
 include(utils/socket/BUILD.cmake)
 include(utils/strconv/BUILD.cmake)
 include(utils/symbol/BUILD.cmake)
+include(utils/system/BUILD.cmake)
 include(utils/text/BUILD.cmake)
 include(utils/traits/BUILD.cmake)
diff --git a/src/tint/utils/system/BUILD.bazel b/src/tint/utils/system/BUILD.bazel
new file mode 100644
index 0000000..1cac5ca
--- /dev/null
+++ b/src/tint/utils/system/BUILD.bazel
@@ -0,0 +1,121 @@
+# Copyright 2024 The Dawn & Tint Authors
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions and the following disclaimer in the documentation
+#    and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its
+#    contributors may be used to endorse or promote products derived from
+#    this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+################################################################################
+# File generated by 'tools/src/cmd/gen' using the template:
+#   tools/src/cmd/gen/build/BUILD.bazel.tmpl
+#
+# To regenerate run: './tools/run gen'
+#
+#                       Do not modify this file directly
+################################################################################
+
+load("//src/tint:flags.bzl", "COPTS")
+load("@bazel_skylib//lib:selects.bzl", "selects")
+cc_library(
+  name = "system",
+  srcs = [
+  ] + select({
+    ":_not_tint_build_is_linux__and__not_tint_build_is_mac__and__not_tint_build_is_win_": [
+      "terminal_other.cc",
+    ],
+    "//conditions:default": [],
+  }) + select({
+    ":_not_tint_build_is_win_": [
+      "env_other.cc",
+    ],
+    "//conditions:default": [],
+  }) + select({
+    ":tint_build_is_linux_or_tint_build_is_mac": [
+      "terminal_posix.cc",
+    ],
+    "//conditions:default": [],
+  }) + select({
+    ":tint_build_is_win": [
+      "env_windows.cc",
+      "terminal_windows.cc",
+    ],
+    "//conditions:default": [],
+  }),
+  hdrs = [
+    "env.h",
+    "terminal.h",
+  ],
+  deps = [
+    "//src/tint/utils/macros",
+  ],
+  copts = COPTS,
+  visibility = ["//visibility:public"],
+)
+
+alias(
+  name = "tint_build_is_linux",
+  actual = "//src/tint:tint_build_is_linux_true",
+)
+
+alias(
+  name = "_not_tint_build_is_linux_",
+  actual = "//src/tint:tint_build_is_linux_false",
+)
+
+alias(
+  name = "tint_build_is_mac",
+  actual = "//src/tint:tint_build_is_mac_true",
+)
+
+alias(
+  name = "_not_tint_build_is_mac_",
+  actual = "//src/tint:tint_build_is_mac_false",
+)
+
+alias(
+  name = "tint_build_is_win",
+  actual = "//src/tint:tint_build_is_win_true",
+)
+
+alias(
+  name = "_not_tint_build_is_win_",
+  actual = "//src/tint:tint_build_is_win_false",
+)
+
+selects.config_setting_group(
+    name = "tint_build_is_linux_or_tint_build_is_mac",
+    match_any = [
+        "tint_build_is_linux",
+        "tint_build_is_mac",
+    ],
+)
+
+selects.config_setting_group(
+    name = "_not_tint_build_is_linux__and__not_tint_build_is_mac__and__not_tint_build_is_win_",
+    match_all = [
+        ":_not_tint_build_is_linux_",
+        ":_not_tint_build_is_mac_",
+        ":_not_tint_build_is_win_",
+    ],
+)
+
diff --git a/src/tint/utils/system/BUILD.cmake b/src/tint/utils/system/BUILD.cmake
new file mode 100644
index 0000000..748da7d
--- /dev/null
+++ b/src/tint/utils/system/BUILD.cmake
@@ -0,0 +1,73 @@
+# Copyright 2024 The Dawn & Tint Authors
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions and the following disclaimer in the documentation
+#    and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its
+#    contributors may be used to endorse or promote products derived from
+#    this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+################################################################################
+# File generated by 'tools/src/cmd/gen' using the template:
+#   tools/src/cmd/gen/build/BUILD.cmake.tmpl
+#
+# To regenerate run: './tools/run gen'
+#
+#                       Do not modify this file directly
+################################################################################
+
+################################################################################
+# Target:    tint_utils_system
+# Kind:      lib
+################################################################################
+tint_add_target(tint_utils_system lib
+  utils/system/env.h
+  utils/system/terminal.h
+)
+
+tint_target_add_dependencies(tint_utils_system lib
+  tint_utils_macros
+)
+
+if((NOT TINT_BUILD_IS_LINUX) AND (NOT TINT_BUILD_IS_MAC) AND (NOT TINT_BUILD_IS_WIN))
+  tint_target_add_sources(tint_utils_system lib
+    "utils/system/terminal_other.cc"
+  )
+endif((NOT TINT_BUILD_IS_LINUX) AND (NOT TINT_BUILD_IS_MAC) AND (NOT TINT_BUILD_IS_WIN))
+
+if((NOT TINT_BUILD_IS_WIN))
+  tint_target_add_sources(tint_utils_system lib
+    "utils/system/env_other.cc"
+  )
+endif((NOT TINT_BUILD_IS_WIN))
+
+if(TINT_BUILD_IS_LINUX OR TINT_BUILD_IS_MAC)
+  tint_target_add_sources(tint_utils_system lib
+    "utils/system/terminal_posix.cc"
+  )
+endif(TINT_BUILD_IS_LINUX OR TINT_BUILD_IS_MAC)
+
+if(TINT_BUILD_IS_WIN)
+  tint_target_add_sources(tint_utils_system lib
+    "utils/system/env_windows.cc"
+    "utils/system/terminal_windows.cc"
+  )
+endif(TINT_BUILD_IS_WIN)
diff --git a/src/tint/utils/system/BUILD.gn b/src/tint/utils/system/BUILD.gn
new file mode 100644
index 0000000..5f101c1
--- /dev/null
+++ b/src/tint/utils/system/BUILD.gn
@@ -0,0 +1,66 @@
+# Copyright 2024 The Dawn & Tint Authors
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions and the following disclaimer in the documentation
+#    and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its
+#    contributors may be used to endorse or promote products derived from
+#    this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+################################################################################
+# File generated by 'tools/src/cmd/gen' using the template:
+#   tools/src/cmd/gen/build/BUILD.gn.tmpl
+#
+# To regenerate run: './tools/run gen'
+#
+#                       Do not modify this file directly
+################################################################################
+
+import("../../../../scripts/tint_overrides_with_defaults.gni")
+
+import("${tint_src_dir}/tint.gni")
+
+libtint_source_set("system") {
+  sources = [
+    "env.h",
+    "terminal.h",
+  ]
+  deps = [ "${tint_src_dir}/utils/macros" ]
+
+  if (!tint_build_is_linux && !tint_build_is_mac && !tint_build_is_win) {
+    sources += [ "terminal_other.cc" ]
+  }
+
+  if (!tint_build_is_win) {
+    sources += [ "env_other.cc" ]
+  }
+
+  if (tint_build_is_linux || tint_build_is_mac) {
+    sources += [ "terminal_posix.cc" ]
+  }
+
+  if (tint_build_is_win) {
+    sources += [
+      "env_windows.cc",
+      "terminal_windows.cc",
+    ]
+  }
+}
diff --git a/src/tint/utils/system/env.h b/src/tint/utils/system/env.h
new file mode 100644
index 0000000..bcf2b46
--- /dev/null
+++ b/src/tint/utils/system/env.h
@@ -0,0 +1,42 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef SRC_TINT_UTILS_SYSTEM_ENV_H_
+#define SRC_TINT_UTILS_SYSTEM_ENV_H_
+
+#include <string>
+
+namespace tint {
+
+/// @param name the name of the environment variable
+/// @returns the environment variable value with the given name, or an empty string if the variable
+/// was not found.
+std::string GetEnvVar(std::string_view name);
+
+}  // namespace tint
+
+#endif  // SRC_TINT_UTILS_SYSTEM_ENV_H_
diff --git a/src/tint/utils/system/env_other.cc b/src/tint/utils/system/env_other.cc
new file mode 100644
index 0000000..cdf5759
--- /dev/null
+++ b/src/tint/utils/system/env_other.cc
@@ -0,0 +1,44 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// GEN_BUILD:CONDITION(!tint_build_is_win)
+
+#include "src/tint/utils/system/env.h"
+
+#include <cstdlib>
+#include <string_view>
+
+namespace tint {
+
+std::string GetEnvVar(std::string_view name) {
+    if (auto* val = std::getenv(name.data())) {
+        return val;
+    }
+    return "";
+}
+
+}  // namespace tint
diff --git a/src/tint/utils/system/env_windows.cc b/src/tint/utils/system/env_windows.cc
new file mode 100644
index 0000000..534b358
--- /dev/null
+++ b/src/tint/utils/system/env_windows.cc
@@ -0,0 +1,49 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// GEN_BUILD:CONDITION(tint_build_is_win)
+
+#include "src/tint/utils/system/env.h"
+
+#include <stdlib.h>
+#include <string_view>
+
+namespace tint {
+
+std::string GetEnvVar(std::string_view name) {
+    // Use _dupenv_s to avoid unsafe warnings about std::getenv
+    char* value = nullptr;
+    _dupenv_s(&value, nullptr, name.data());
+    if (value) {
+        std::string result = value;
+        free(value);
+        return result;
+    }
+    return "";
+}
+
+}  // namespace tint
diff --git a/src/tint/utils/system/terminal.h b/src/tint/utils/system/terminal.h
new file mode 100644
index 0000000..2374496
--- /dev/null
+++ b/src/tint/utils/system/terminal.h
@@ -0,0 +1,49 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef SRC_TINT_UTILS_SYSTEM_TERMINAL_H_
+#define SRC_TINT_UTILS_SYSTEM_TERMINAL_H_
+
+#include <cstdio>
+#include <optional>
+
+namespace tint {
+
+/// Detects whether the terminal at @p out supports color output.
+/// @param out the file to print to.
+/// @return true if the terminal supports colors.
+bool TerminalSupportsColors(FILE* out);
+
+/// Attempts to detect whether the terminal at @p out is dark.
+/// @param out the file to print to.
+/// @returns true if the terminal is dark, false if the terminal is light, or nullopt if
+/// unknown.
+std::optional<bool> TerminalIsDark(FILE* out);
+
+}  // namespace tint
+
+#endif  // SRC_TINT_UTILS_SYSTEM_TERMINAL_H_
diff --git a/src/tint/utils/system/terminal_other.cc b/src/tint/utils/system/terminal_other.cc
new file mode 100644
index 0000000..96e7263
--- /dev/null
+++ b/src/tint/utils/system/terminal_other.cc
@@ -0,0 +1,42 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// GEN_BUILD:CONDITION((!tint_build_is_linux) && (!tint_build_is_mac) && (!tint_build_is_win))
+
+#include "src/tint/utils/system/terminal.h"
+
+namespace tint {
+
+bool TerminalSupportsColors(FILE*) {
+    return false;
+}
+
+std::optional<bool> TerminalIsDark(FILE*) {
+    return std::nullopt;
+}
+
+}  // namespace tint
diff --git a/src/tint/utils/system/terminal_posix.cc b/src/tint/utils/system/terminal_posix.cc
new file mode 100644
index 0000000..699966d
--- /dev/null
+++ b/src/tint/utils/system/terminal_posix.cc
@@ -0,0 +1,184 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// GEN_BUILD:CONDITION(tint_build_is_linux || tint_build_is_mac)
+
+#include <unistd.h>
+
+#include <termios.h>
+#include <chrono>
+#include <cstdint>
+#include <cstdio>
+#include <cstring>
+#include <optional>
+#include <string_view>
+#include <utility>
+
+#include "src/tint/utils/macros/defer.h"
+#include "src/tint/utils/system/env.h"
+#include "src/tint/utils/system/terminal.h"
+
+namespace tint {
+
+bool TerminalSupportsColors(FILE* f) {
+    if (!isatty(fileno(f))) {
+        return false;
+    }
+
+    if (auto term = GetEnvVar("TERM"); !term.empty()) {
+        return term == "cygwin" || term == "linux" || term == "rxvt-unicode-256color" ||
+               term == "rxvt-unicode" || term == "screen-256color" || term == "screen" ||
+               term == "tmux-256color" || term == "tmux" || term == "xterm-256color" ||
+               term == "xterm-color" || term == "xterm";
+    }
+
+    return false;
+}
+
+/// Probes the terminal using a Device Control escape sequence to get the background color.
+/// @see https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Device-Control-functions
+std::optional<bool> TerminalIsDark(FILE* out) {
+    if (!TerminalSupportsColors(out)) {
+        return std::nullopt;
+    }
+
+    // Get the file descriptor for 'out'
+    int out_fd = fileno(out);
+    if (out_fd == -1) {
+        return std::nullopt;
+    }
+
+    // Store the current attributes for 'out', restore it before returning
+    termios original_state{};
+    tcgetattr(out_fd, &original_state);
+    TINT_DEFER(tcsetattr(out_fd, TCSADRAIN, &original_state));
+
+    // Prevent echoing.
+    termios state = original_state;
+    state.c_lflag &= ~static_cast<tcflag_t>(ECHO | ICANON);
+    tcsetattr(out_fd, TCSADRAIN, &state);
+
+    // Emit the device control escape sequence to query the terminal colors.
+    static constexpr std::string_view kQuery = "\x1b]11;?\x07";
+    fwrite(kQuery.data(), 1, kQuery.length(), out);
+    fflush(out);
+
+    // Timeout for attempting to read the response.
+    static constexpr auto kTimeout = std::chrono::milliseconds(100);
+
+    // Record the start time.
+    auto start = std::chrono::steady_clock::now();
+
+    // Helpers for parsing the response.
+    std::optional<char> peek;
+    auto read = [&]() -> std::optional<char> {
+        if (peek) {
+            char c = *peek;
+            peek.reset();
+            return c;
+        }
+        while ((std::chrono::steady_clock::now() - start) < kTimeout) {
+            char c;
+            if (fread(&c, 1, 1, stdin) == 1) {
+                return c;
+            }
+        }
+        return std::nullopt;
+    };
+
+    auto match = [&](std::string_view str) {
+        for (char c : str) {
+            if (c != read()) {
+                return false;
+            }
+        }
+        return true;
+    };
+
+    struct Hex {
+        uint32_t num = 0;  // The parsed hex number
+        uint32_t len = 0;  // Number of hex digits parsed
+    };
+    auto hex = [&] {
+        uint32_t num = 0;
+        uint32_t len = 0;
+        while (auto c = read()) {
+            if (c >= '0' && c <= '9') {
+                num = num * 16 + static_cast<uint32_t>(*c - '0');
+                len++;
+            } else if (c >= 'a' && c <= 'z') {
+                num = num * 16 + 10 + static_cast<uint32_t>(*c - 'a');
+                len++;
+            } else if (c >= 'A' && c <= 'Z') {
+                num = num * 16 + 10 + static_cast<uint32_t>(*c - 'A');
+                len++;
+            } else {
+                peek = c;
+                break;
+            }
+        }
+        return Hex{num, len};
+    };
+
+    if (!match("\033]11;rgb:")) {
+        return std::nullopt;
+    }
+
+    auto r_i = hex();
+    if (!match("/")) {
+        return std::nullopt;
+    }
+    auto g_i = hex();
+    if (!match("/")) {
+        return std::nullopt;
+    }
+    auto b_i = hex();
+
+    if (r_i.len != g_i.len || g_i.len != b_i.len) {
+        return std::nullopt;
+    }
+
+    uint32_t max = 0;
+    switch (r_i.len) {
+        case 2:  // rr/gg/bb
+            max = 0xff;
+            break;
+        case 4:  // rrrr/gggg/bbbb
+            max = 0xffff;
+            break;
+        default:
+            return std::nullopt;
+    }
+
+    // https://en.wikipedia.org/wiki/Relative_luminance
+    float r = static_cast<float>(r_i.num) / static_cast<float>(max);
+    float g = static_cast<float>(g_i.num) / static_cast<float>(max);
+    float b = static_cast<float>(b_i.num) / static_cast<float>(max);
+    return (0.2126f * r + 0.7152f * g + 0.0722f * b) < 0.5f;
+}
+
+}  // namespace tint
diff --git a/src/tint/utils/system/terminal_windows.cc b/src/tint/utils/system/terminal_windows.cc
new file mode 100644
index 0000000..4ed2f78
--- /dev/null
+++ b/src/tint/utils/system/terminal_windows.cc
@@ -0,0 +1,92 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// GEN_BUILD:CONDITION(tint_build_is_win)
+
+#include "src/tint/utils/macros/defer.h"
+#include "src/tint/utils/system/env.h"
+#include "src/tint/utils/system/terminal.h"
+
+#define WIN32_LEAN_AND_MEAN 1
+#include <Windows.h>
+
+namespace tint {
+namespace {
+
+HANDLE ConsoleHandleFrom(FILE* file) {
+    HANDLE handle = INVALID_HANDLE_VALUE;
+    if (file == stdout) {
+        handle = GetStdHandle(STD_OUTPUT_HANDLE);
+    } else if (file == stderr) {
+        handle = GetStdHandle(STD_ERROR_HANDLE);
+    } else {
+        return INVALID_HANDLE_VALUE;
+    }
+
+    CONSOLE_SCREEN_BUFFER_INFO info{};
+    if (GetConsoleScreenBufferInfo(handle, &info) == 0) {
+        return INVALID_HANDLE_VALUE;
+    }
+    return handle;
+}
+
+}  // namespace
+
+bool TerminalSupportsColors(FILE* out) {
+    return ConsoleHandleFrom(out) != INVALID_HANDLE_VALUE;
+}
+
+std::optional<bool> TerminalIsDark(FILE* out) {
+    if (!GetEnvVar("WT_SESSION").empty()) {
+        // Windows terminal does not support querying the palette
+        // See: https://github.com/microsoft/terminal/issues/10639
+        return std::nullopt;
+    }
+
+    if (HANDLE handle = ConsoleHandleFrom(out); handle != INVALID_HANDLE_VALUE) {
+        if (HANDLE screen_buffer = CreateConsoleScreenBuffer(GENERIC_READ, FILE_SHARE_READ,
+                                                             /* lpSecurityAttributes */ nullptr,
+                                                             CONSOLE_TEXTMODE_BUFFER, nullptr)) {
+            TINT_DEFER(CloseHandle(screen_buffer));
+            CONSOLE_SCREEN_BUFFER_INFOEX info{};
+            info.cbSize = sizeof(info);
+            if (GetConsoleScreenBufferInfoEx(screen_buffer, &info)) {
+                COLORREF background = info.ColorTable[(info.wAttributes & 0xf0) >> 4];
+                // https://en.wikipedia.org/wiki/Relative_luminance
+                float r = static_cast<float>((background >> 0) & 0xff) / 255.0f;
+                float g = static_cast<float>((background >> 8) & 0xff) / 255.0f;
+                float b = static_cast<float>((background >> 16) & 0xff) / 255.0f;
+                return (0.2126f * r + 0.7152f * g + 0.0722f * b) < 0.5f;
+            }
+        }
+    }
+
+    // Unknown
+    return std::nullopt;
+}
+
+}  // namespace tint
diff --git a/src/tint/utils/text/BUILD.bazel b/src/tint/utils/text/BUILD.bazel
index 0e7ee03..e73f66e 100644
--- a/src/tint/utils/text/BUILD.bazel
+++ b/src/tint/utils/text/BUILD.bazel
@@ -80,6 +80,7 @@
     "//src/tint/utils/math",
     "//src/tint/utils/memory",
     "//src/tint/utils/rtti",
+    "//src/tint/utils/system",
     "//src/tint/utils/traits",
   ],
   copts = COPTS,
diff --git a/src/tint/utils/text/BUILD.cmake b/src/tint/utils/text/BUILD.cmake
index 7b19a11..2aa69bf 100644
--- a/src/tint/utils/text/BUILD.cmake
+++ b/src/tint/utils/text/BUILD.cmake
@@ -64,6 +64,7 @@
   tint_utils_math
   tint_utils_memory
   tint_utils_rtti
+  tint_utils_system
   tint_utils_traits
 )
 
diff --git a/src/tint/utils/text/BUILD.gn b/src/tint/utils/text/BUILD.gn
index 8aaadd2..5460394 100644
--- a/src/tint/utils/text/BUILD.gn
+++ b/src/tint/utils/text/BUILD.gn
@@ -68,6 +68,7 @@
     "${tint_src_dir}/utils/math",
     "${tint_src_dir}/utils/memory",
     "${tint_src_dir}/utils/rtti",
+    "${tint_src_dir}/utils/system",
     "${tint_src_dir}/utils/traits",
   ]
 
diff --git a/src/tint/utils/text/styled_text_printer.cc b/src/tint/utils/text/styled_text_printer.cc
index be83c03..557b3ff 100644
--- a/src/tint/utils/text/styled_text_printer.cc
+++ b/src/tint/utils/text/styled_text_printer.cc
@@ -27,6 +27,7 @@
 
 #include <cstring>
 
+#include "src/tint/utils/system/terminal.h"
 #include "src/tint/utils/text/styled_text_printer.h"
 
 namespace tint {
@@ -51,7 +52,8 @@
     return std::make_unique<Plain>(out);
 }
 std::unique_ptr<StyledTextPrinter> StyledTextPrinter::Create(FILE* out) {
-    return Create(out, StyledTextTheme::kDefault);
+    bool is_dark = TerminalIsDark(out).value_or(true);
+    return Create(out, is_dark ? StyledTextTheme::kDefaultDark : StyledTextTheme::kDefaultLight);
 }
 
 StyledTextPrinter::~StyledTextPrinter() = default;
diff --git a/src/tint/utils/text/styled_text_printer_other.cc b/src/tint/utils/text/styled_text_printer_other.cc
index eec09c0..4f32526 100644
--- a/src/tint/utils/text/styled_text_printer_other.cc
+++ b/src/tint/utils/text/styled_text_printer_other.cc
@@ -27,8 +27,6 @@
 
 // GEN_BUILD:CONDITION((!tint_build_is_linux) && (!tint_build_is_mac) && (!tint_build_is_win))
 
-#include <cstring>
-
 #include "src/tint/utils/text/styled_text_printer.h"
 
 namespace tint {
diff --git a/src/tint/utils/text/styled_text_printer_posix.cc b/src/tint/utils/text/styled_text_printer_posix.cc
index e03ab52..7d34fed 100644
--- a/src/tint/utils/text/styled_text_printer_posix.cc
+++ b/src/tint/utils/text/styled_text_printer_posix.cc
@@ -28,43 +28,16 @@
 // GEN_BUILD:CONDITION(tint_build_is_linux || tint_build_is_mac)
 
 #include <unistd.h>
+#include <memory>
 
-#include <cstring>
-
-#include "src/tint/utils/text/styled_text.h"
+#include "src/tint/utils/system/terminal.h"
 #include "src/tint/utils/text/styled_text_printer.h"
-#include "src/tint/utils/text/styled_text_theme.h"
-#include "src/tint/utils/text/text_style.h"
 
 namespace tint {
-namespace {
-
-bool SupportsANSIEscape(FILE* f) {
-    if (!isatty(fileno(f))) {
-        return false;
-    }
-
-    const char* cterm = getenv("TERM");
-    if (cterm == nullptr) {
-        return false;
-    }
-
-    std::string term = getenv("TERM");
-    if (term != "cygwin" && term != "linux" && term != "rxvt-unicode-256color" &&
-        term != "rxvt-unicode" && term != "screen-256color" && term != "screen" &&
-        term != "tmux-256color" && term != "tmux" && term != "xterm-256color" &&
-        term != "xterm-color" && term != "xterm") {
-        return false;
-    }
-
-    return true;
-}
-
-}  // namespace
 
 std::unique_ptr<StyledTextPrinter> StyledTextPrinter::Create(FILE* out,
                                                              const StyledTextTheme& theme) {
-    if (SupportsANSIEscape(out)) {
+    if (TerminalSupportsColors(out)) {
         return CreateANSI(out, theme);
     }
     return CreatePlain(out);
diff --git a/src/tint/utils/text/styled_text_theme.cc b/src/tint/utils/text/styled_text_theme.cc
index ba2944a..e243cd5 100644
--- a/src/tint/utils/text/styled_text_theme.cc
+++ b/src/tint/utils/text/styled_text_theme.cc
@@ -30,7 +30,7 @@
 
 namespace tint {
 
-const StyledTextTheme StyledTextTheme::kDefault{
+const StyledTextTheme StyledTextTheme::kDefaultDark{
     /* compare_match */ StyledTextTheme::Attributes{
         /* foreground */ std::nullopt,
         /* background */ Color{20, 100, 20},
@@ -137,6 +137,113 @@
     },
 };
 
+const StyledTextTheme StyledTextTheme::kDefaultLight{
+    /* compare_match */ StyledTextTheme::Attributes{
+        /* foreground */ std::nullopt,
+        /* background */ Color{190, 240, 190},
+        /* bold */ std::nullopt,
+        /* underlined */ std::nullopt,
+    },
+    /* compare_mismatch */
+    StyledTextTheme::Attributes{
+        /* foreground */ std::nullopt,
+        /* background */ Color{240, 190, 190},
+        /* bold */ std::nullopt,
+        /* underlined */ std::nullopt,
+    },
+    /* severity_success */
+    StyledTextTheme::Attributes{
+        /* foreground */ Color{0, 200, 0},
+        /* background */ std::nullopt,
+        /* bold */ std::nullopt,
+        /* underlined */ std::nullopt,
+    },
+    /* severity_warning */
+    StyledTextTheme::Attributes{
+        /* foreground */ Color{200, 200, 0},
+        /* background */ std::nullopt,
+        /* bold */ std::nullopt,
+        /* underlined */ std::nullopt,
+    },
+    /* severity_failure */
+    StyledTextTheme::Attributes{
+        /* foreground */ Color{200, 0, 0},
+        /* background */ std::nullopt,
+        /* bold */ std::nullopt,
+        /* underlined */ std::nullopt,
+    },
+    /* severity_fatal */
+    StyledTextTheme::Attributes{
+        /* foreground */ Color{200, 0, 200},
+        /* background */ std::nullopt,
+        /* bold */ std::nullopt,
+        /* underlined */ std::nullopt,
+    },
+    /* kind_code */
+    StyledTextTheme::Attributes{
+        /* foreground */ Color{10, 10, 10},
+        /* background */ Color{248, 248, 248},
+        /* bold */ std::nullopt,
+        /* underlined */ std::nullopt,
+    },
+    /* kind_keyword */
+    StyledTextTheme::Attributes{
+        /* foreground */ Color{175, 0, 219},
+        /* background */ std::nullopt,
+        /* bold */ std::nullopt,
+        /* underlined */ std::nullopt,
+    },
+    /* kind_variable */
+    StyledTextTheme::Attributes{
+        /* foreground */ Color{0, 16, 128},
+        /* background */ std::nullopt,
+        /* bold */ std::nullopt,
+        /* underlined */ std::nullopt,
+    },
+    /* kind_type */
+    StyledTextTheme::Attributes{
+        /* foreground */ Color{38, 127, 153},
+        /* background */ std::nullopt,
+        /* bold */ std::nullopt,
+        /* underlined */ std::nullopt,
+    },
+    /* kind_function */
+    StyledTextTheme::Attributes{
+        /* foreground */ Color{121, 94, 38},
+        /* background */ std::nullopt,
+        /* bold */ std::nullopt,
+        /* underlined */ std::nullopt,
+    },
+    /* kind_enum */
+    StyledTextTheme::Attributes{
+        /* foreground */ Color{0, 112, 193},
+        /* background */ std::nullopt,
+        /* bold */ std::nullopt,
+        /* underlined */ std::nullopt,
+    },
+    /* kind_literal */
+    StyledTextTheme::Attributes{
+        /* foreground */ Color{9, 134, 88},
+        /* background */ std::nullopt,
+        /* bold */ std::nullopt,
+        /* underlined */ std::nullopt,
+    },
+    /* kind_attribute */
+    StyledTextTheme::Attributes{
+        /* foreground */ Color{156, 220, 254},
+        /* background */ std::nullopt,
+        /* bold */ std::nullopt,
+        /* underlined */ std::nullopt,
+    },
+    /* kind_squiggle */
+    StyledTextTheme::Attributes{
+        /* foreground */ Color{0, 200, 255},
+        /* background */ std::nullopt,
+        /* bold */ std::nullopt,
+        /* underlined */ std::nullopt,
+    },
+};
+
 StyledTextTheme::Attributes StyledTextTheme::Get(TextStyle text_style) const {
     Attributes out;
     out.bold = false;
diff --git a/src/tint/utils/text/styled_text_theme.h b/src/tint/utils/text/styled_text_theme.h
index c9dcfbb..b36db4f 100644
--- a/src/tint/utils/text/styled_text_theme.h
+++ b/src/tint/utils/text/styled_text_theme.h
@@ -40,8 +40,10 @@
 
 /// StyledTextTheme describes the display theming for TextStyles
 struct StyledTextTheme {
-    /// The default theme
-    static const StyledTextTheme kDefault;
+    /// The default dark theme
+    static const StyledTextTheme kDefaultDark;
+    /// The default light theme
+    static const StyledTextTheme kDefaultLight;
 
     /// Color holds a 24-bit RGB color
     struct Color {