Skip to content

Themes API Reference

The theme system provides professionally-curated color palettes for coordinated lighting across LIFX devices.

Theme Class

The Theme class represents a collection of HSBK colors forming a coordinated palette.

Theme

Theme(
    colors: list[HSBK] | None = None,
    *,
    slug: str | None = None,
    name: str | None = None,
    category: str | None = None,
    disposition: Disposition | None = None,
    replaced_by: str | None = None,
)

A collection of colors representing a theme or color palette.

Themes can be applied to LIFX devices to coordinate colors across multiple lights. Supports both single-zone and multi-zone devices.

ATTRIBUTE DESCRIPTION
colors

List of HSBK colors in the theme

TYPE: list[HSBK]

slug

Library key for a theme from ThemeLibrary (None for a caller-constructed theme)

name

Display name for a theme from ThemeLibrary (None for a caller-constructed theme)

category

Category for a theme from ThemeLibrary (None for a caller-constructed theme)

disposition

Recorded fate of a theme from ThemeLibrary (None for a caller-constructed theme)

replaced_by

Successor key of a deprecated or renamed theme from ThemeLibrary; None unless disposition is "deprecated" or "renamed" (and None for a caller-constructed theme)

Note

shuffled() returns an identity-less copy: slug, name, category, disposition and replaced_by do not propagate. This is a known deferred limitation of the identity round-trip guarantee. (random() returns a single HSBK, not a Theme, so it carries no identity to begin with.)

Note

== compares identity, so a Theme stays hashable and usable as a dict key or set member. To compare palettes, call palette_equals().

Example
# Create a theme with specific colors
theme = Theme(
    [
        HSBK(hue=0, saturation=1.0, brightness=1.0, kelvin=3500),  # Red
        HSBK(hue=120, saturation=1.0, brightness=1.0, kelvin=3500),  # Green
        HSBK(hue=240, saturation=1.0, brightness=1.0, kelvin=3500),  # Blue
    ]
)

# Access colors
for color in theme:
    print(f"Color: {color.hue}°")

# Get a specific color
first_color = theme[0]

# Add more colors
theme.add_color(HSBK(hue=180, saturation=1.0, brightness=1.0, kelvin=3500))
PARAMETER DESCRIPTION
colors

List of HSBK colors (defaults to white if None or empty)

TYPE: list[HSBK] | None DEFAULT: None

slug

Library key for the theme (attached by ThemeLibrary)

TYPE: str | None DEFAULT: None

name

Display name for the theme (attached by ThemeLibrary)

TYPE: str | None DEFAULT: None

category

Category for the theme (attached by ThemeLibrary)

TYPE: str | None DEFAULT: None

disposition

Recorded fate of the theme (attached by ThemeLibrary)

TYPE: Disposition | None DEFAULT: None

replaced_by

Successor key of a deprecated or renamed theme (attached by ThemeLibrary); None unless disposition is "deprecated" or "renamed"

TYPE: str | None DEFAULT: None

Example
# Create from list of colors
theme = Theme([color1, color2, color3])

# Create with default white color
theme = Theme()
METHOD DESCRIPTION
add_color

Add a color to the theme.

random

Get a random color from the theme.

shuffled

Get a new theme with colors in random order.

get_next_bounds_checked

Get the next color after index or the last color if at end.

ensure_color

Ensure the theme has at least one color.

__len__

Get the number of colors in the theme.

__iter__

Iterate over colors in the theme.

__getitem__

Get a color by index.

__contains__

Check if a color is in the theme.

palette_equals

Check whether two themes carry the same palette.

__repr__

Return a string representation of the theme.

Source code in src/lifx/theme/theme.py
def __init__(
    self,
    colors: list[HSBK] | None = None,
    *,
    slug: str | None = None,
    name: str | None = None,
    category: str | None = None,
    disposition: Disposition | None = None,
    replaced_by: str | None = None,
) -> None:
    """Create a new theme with the given colors.

    Args:
        colors: List of HSBK colors (defaults to white if None or empty)
        slug: Library key for the theme (attached by ``ThemeLibrary``)
        name: Display name for the theme (attached by ``ThemeLibrary``)
        category: Category for the theme (attached by ``ThemeLibrary``)
        disposition: Recorded fate of the theme (attached by
            ``ThemeLibrary``)
        replaced_by: Successor key of a deprecated or renamed theme
            (attached by ``ThemeLibrary``); None unless ``disposition``
            is ``"deprecated"`` or ``"renamed"``

    Example:
        ```python
        # Create from list of colors
        theme = Theme([color1, color2, color3])

        # Create with default white color
        theme = Theme()
        ```
    """
    if colors and len(colors) > 0:
        # Copied, never aliased: a Theme built over a caller's list would
        # otherwise mutate that list through add_color(), and a Theme
        # built over a cached or shared list would let add_color() corrupt
        # the source. Every construction path gets the isolation, not just
        # ThemeLibrary.get().
        self.colors: list[HSBK] = list(colors)
    else:
        # Default to white if no colors provided
        self.colors = [Colors.WHITE_NEUTRAL]
    self.slug = slug
    self.name = name
    self.category = category
    self.disposition = disposition
    self.replaced_by = replaced_by

Methods:

add_color
add_color(color: HSBK) -> None

Add a color to the theme.

PARAMETER DESCRIPTION
color

HSBK color to add

TYPE: HSBK

Example
theme = Theme()
theme.add_color(HSBK(hue=0, saturation=1.0, brightness=1.0, kelvin=3500))
Source code in src/lifx/theme/theme.py
def add_color(self, color: HSBK) -> None:
    """Add a color to the theme.

    Args:
        color: HSBK color to add

    Example:
        ```python
        theme = Theme()
        theme.add_color(HSBK(hue=0, saturation=1.0, brightness=1.0, kelvin=3500))
        ```
    """
    self.colors.append(color)
random
random() -> HSBK

Get a random color from the theme.

RETURNS DESCRIPTION
HSBK

A random HSBK color from the theme

Example
theme = Theme([red, green, blue])
color = theme.random()
Source code in src/lifx/theme/theme.py
def random(self) -> HSBK:
    """Get a random color from the theme.

    Returns:
        A random HSBK color from the theme

    Example:
        ```python
        theme = Theme([red, green, blue])
        color = theme.random()
        ```
    """
    return random.choice(self.colors)
shuffled
shuffled() -> Theme

Get a new theme with colors in random order.

RETURNS DESCRIPTION
Theme

New Theme instance with shuffled colors

Example
theme = Theme([color1, color2, color3])
shuffled_theme = theme.shuffled()
Source code in src/lifx/theme/theme.py
def shuffled(self) -> Theme:
    """Get a new theme with colors in random order.

    Returns:
        New Theme instance with shuffled colors

    Example:
        ```python
        theme = Theme([color1, color2, color3])
        shuffled_theme = theme.shuffled()
        ```
    """
    shuffled_colors = self.colors.copy()
    random.shuffle(shuffled_colors)
    return Theme(shuffled_colors)
get_next_bounds_checked
get_next_bounds_checked(index: int) -> HSBK

Get the next color after index or the last color if at end.

PARAMETER DESCRIPTION
index

Index of current color

TYPE: int

RETURNS DESCRIPTION
HSBK

Next HSBK color or the last color if index is at the end

Example
theme = Theme([red, green, blue])
color = theme.get_next_bounds_checked(0)  # green
color = theme.get_next_bounds_checked(2)  # blue (last color)
Source code in src/lifx/theme/theme.py
def get_next_bounds_checked(self, index: int) -> HSBK:
    """Get the next color after index or the last color if at end.

    Args:
        index: Index of current color

    Returns:
        Next HSBK color or the last color if index is at the end

    Example:
        ```python
        theme = Theme([red, green, blue])
        color = theme.get_next_bounds_checked(0)  # green
        color = theme.get_next_bounds_checked(2)  # blue (last color)
        ```
    """
    if index + 1 < len(self.colors):
        return self.colors[index + 1]
    return self.colors[-1]
ensure_color
ensure_color() -> None

Ensure the theme has at least one color.

If the theme is empty, adds a default white color.

Source code in src/lifx/theme/theme.py
def ensure_color(self) -> None:
    """Ensure the theme has at least one color.

    If the theme is empty, adds a default white color.
    """
    if not self.colors:
        self.colors.append(
            HSBK(hue=0, saturation=0, brightness=1.0, kelvin=3500)
        )  # pragma: no cover
__len__
__len__() -> int

Get the number of colors in the theme.

Source code in src/lifx/theme/theme.py
def __len__(self) -> int:
    """Get the number of colors in the theme."""
    return len(self.colors)
__iter__
__iter__() -> Iterator[HSBK]

Iterate over colors in the theme.

Source code in src/lifx/theme/theme.py
def __iter__(self) -> Iterator[HSBK]:
    """Iterate over colors in the theme."""
    return iter(self.colors)
__getitem__
__getitem__(index: int) -> HSBK

Get a color by index.

PARAMETER DESCRIPTION
index

Index of the color (0-based)

TYPE: int

RETURNS DESCRIPTION
HSBK

HSBK color at the given index

RAISES DESCRIPTION
IndexError

If index is out of range

Example
theme = Theme([red, green, blue])
color = theme[1]  # green
Source code in src/lifx/theme/theme.py
def __getitem__(self, index: int) -> HSBK:
    """Get a color by index.

    Args:
        index: Index of the color (0-based)

    Returns:
        HSBK color at the given index

    Raises:
        IndexError: If index is out of range

    Example:
        ```python
        theme = Theme([red, green, blue])
        color = theme[1]  # green
        ```
    """
    return self.colors[index]
__contains__
__contains__(color: HSBK) -> bool

Check if a color is in the theme.

PARAMETER DESCRIPTION
color

HSBK color to check

TYPE: HSBK

RETURNS DESCRIPTION
bool

True if color is in theme (by value comparison)

Example
theme = Theme([red, green, blue])
if red in theme:
    print("Red is in the theme")
Source code in src/lifx/theme/theme.py
def __contains__(self, color: HSBK) -> bool:
    """Check if a color is in the theme.

    Args:
        color: HSBK color to check

    Returns:
        True if color is in theme (by value comparison)

    Example:
        ```python
        theme = Theme([red, green, blue])
        if red in theme:
            print("Red is in the theme")
        ```
    """
    return any(c == color for c in self.colors)
palette_equals
palette_equals(other: Theme) -> bool

Check whether two themes carry the same palette.

Order is never compared because the app shuffles palette order on every application, so two orderings of one palette are the same palette. Identity (slug, name, category, disposition and replaced_by) is excluded too: an identity-bearing library theme and a caller-built theme with the same colors have the same palette. Colors compare at uint16 (protocol) granularity via HSBK equality, and duplicate counts matter — a multiset comparison, not a set comparison.

This is deliberately a named method rather than __eq__. A Theme's palette is mutable via add_color(), so value equality could not be paired with a stable __hash__; making == compare palettes would leave Theme unhashable and silently change what theme in [a, b], list.index() and list.remove() mean. == therefore stays identity comparison and the palette comparison is spelled out at the call site.

PARAMETER DESCRIPTION
other

Theme to compare palettes with.

TYPE: Theme

RETURNS DESCRIPTION
bool

True if both palettes are the same multiset of colors.

Example
# independence and old_glory ship one shared app palette
assert ThemeLibrary.get("independence").palette_equals(
    ThemeLibrary.get("old_glory")
)
Source code in src/lifx/theme/theme.py
def palette_equals(self, other: Theme) -> bool:
    """Check whether two themes carry the same palette.

    Order is never compared because the app shuffles palette order on
    every application, so two orderings of one palette are the same
    palette. Identity (slug, name, category, disposition and
    replaced_by) is excluded too: an identity-bearing library theme and
    a caller-built theme with the same colors have the same palette.
    Colors compare at uint16 (protocol) granularity via HSBK equality,
    and duplicate counts matter — a multiset comparison, not a set
    comparison.

    This is deliberately a named method rather than ``__eq__``. A
    Theme's palette is mutable via ``add_color()``, so value equality
    could not be paired with a stable ``__hash__``; making ``==``
    compare palettes would leave Theme unhashable and silently change
    what ``theme in [a, b]``, ``list.index()`` and ``list.remove()``
    mean. ``==`` therefore stays identity comparison and the palette
    comparison is spelled out at the call site.

    Args:
        other: Theme to compare palettes with.

    Returns:
        True if both palettes are the same multiset of colors.

    Example:
        ```python
        # independence and old_glory ship one shared app palette
        assert ThemeLibrary.get("independence").palette_equals(
            ThemeLibrary.get("old_glory")
        )
        ```
    """
    if not isinstance(other, Theme):
        raise TypeError(
            f"palette_equals() expects a Theme, got {type(other).__name__}"
        )
    return Counter(self.colors) == Counter(other.colors)
__repr__
__repr__() -> str

Return a string representation of the theme.

Source code in src/lifx/theme/theme.py
def __repr__(self) -> str:
    """Return a string representation of the theme."""
    color_count = len(self.colors)
    return f"Theme({color_count} colors)"

ThemeLibrary Class

The ThemeLibrary provides access to 166 themes, resolvable under 168 names.

ThemeLibrary

Collection of built-in colour themes for LIFX devices.

Provides access to every theme in the LIFX app (sport themes excluded) plus the pre-6.3.0 library keys, organised by the app's own categories.

Example
# Get a specific theme
evening_theme = ThemeLibrary.get("evening")

# List all available themes
all_themes = ThemeLibrary.get_available_themes()

# Get themes by category
categories = ThemeLibrary.get_categories()
holidays = ThemeLibrary.get_by_category("Holidays")

# Apply to a light
await light.apply_theme(evening_theme, power_on=True)
METHOD DESCRIPTION
get

Get a theme by name.

get_available_themes

Get all available themes by name.

get_categories

Get every category present in the library's data.

get_by_category

Get all themes in a category.

Methods:

get classmethod
get(name: str) -> Theme

Get a theme by name.

PARAMETER DESCRIPTION
name

Theme name (case-insensitive)

TYPE: str

RETURNS DESCRIPTION
Theme

Theme object

RAISES DESCRIPTION
KeyError

If theme name is not found

Example
from lifx.theme import ThemeLibrary

evening_theme = ThemeLibrary.get("evening")
await light.apply_theme(evening_theme, power_on=True)
Source code in src/lifx/theme/library.py
@classmethod
def get(cls, name: str) -> Theme:
    """Get a theme by name.

    Args:
        name: Theme name (case-insensitive)

    Returns:
        Theme object

    Raises:
        KeyError: If theme name is not found

    Example:
        ```python
        from lifx.theme import ThemeLibrary

        evening_theme = ThemeLibrary.get("evening")
        await light.apply_theme(evening_theme, power_on=True)
        ```
    """
    record = cls._THEMES.get(name.lower())
    if record is None:
        raise KeyError(
            f"Theme '{name}' not found. Use "
            f"ThemeLibrary.get_available_themes() to list the "
            f"available themes."
        )
    # Theme.__init__ copies the palette, so mutating a returned Theme can
    # never corrupt the library's own record.
    return Theme(
        list(record.colors),
        slug=record.slug,
        name=record.name,
        category=record.category,
        disposition=record.disposition,
        replaced_by=record.replaced_by,
    )
get_available_themes classmethod
get_available_themes() -> list[str]

Get all available themes by name.

RETURNS DESCRIPTION
list[str]

Sorted list of theme names

Example
from lifx.theme import ThemeLibrary

all_themes = ThemeLibrary.get_available_themes()
for theme_name in all_themes:
    print(f"- {theme_name}")
Source code in src/lifx/theme/library.py
@classmethod
def get_available_themes(cls) -> list[str]:
    """Get all available themes by name.

    Returns:
        Sorted list of theme names

    Example:
        ```python
        from lifx.theme import ThemeLibrary

        all_themes = ThemeLibrary.get_available_themes()
        for theme_name in all_themes:
            print(f"- {theme_name}")
        ```
    """
    return sorted(cls._THEMES)
get_categories classmethod
get_categories() -> list[str]

Get every category present in the library's data.

RETURNS DESCRIPTION
list[str]

Sorted list[str] of the category names present in the

list[str]

library's theme records.

Example
from lifx.theme import ThemeLibrary

for category in ThemeLibrary.get_categories():
    print(f"- {category}")
Source code in src/lifx/theme/library.py
@classmethod
def get_categories(cls) -> list[str]:
    """Get every category present in the library's data.

    Returns:
        Sorted ``list[str]`` of the category names present in the
        library's theme records.

    Example:
        ```python
        from lifx.theme import ThemeLibrary

        for category in ThemeLibrary.get_categories():
            print(f"- {category}")
        ```
    """
    return sorted({record.category for record in cls._THEMES.values()})
get_by_category classmethod
get_by_category(category: str) -> dict[str, Theme]

Get all themes in a category.

PARAMETER DESCRIPTION
category

Category name. Matching is case- and punctuation- insensitive: both sides are normalised by the slug rule, so "Art Series", "art series" and "art_series" all resolve. The categories are Archives, Art Series, Holidays, Library (pre-6.3.0 keys with no app counterpart, defined by this library rather than the LIFX app), Moods, Music, Nature, Play and Space.

TYPE: str

RETURNS DESCRIPTION
dict[str, Theme]

Dictionary of Theme objects in the category, keyed by slug and

dict[str, Theme]

sorted by slug.

RAISES DESCRIPTION
ValueError

If category is not a string, or names no category in the library. The pre-6.4.0 names (seasonal, holiday, mood, ambient, functional, atmosphere) are among the unrecognised: they were never a taxonomy this data carries, and the message lists the categories that exist.

Source code in src/lifx/theme/library.py
@classmethod
def get_by_category(cls, category: str) -> dict[str, Theme]:
    """Get all themes in a category.

    Args:
        category: Category name. Matching is case- and punctuation-
            insensitive: both sides are normalised by the slug rule, so
            ``"Art Series"``, ``"art series"`` and ``"art_series"`` all
            resolve. The categories are Archives, Art Series, Holidays,
            Library (pre-6.3.0 keys with no app counterpart, defined by
            this library rather than the LIFX app), Moods, Music, Nature,
            Play and Space.

    Returns:
        Dictionary of Theme objects in the category, keyed by slug and
        sorted by slug.

    Raises:
        ValueError: If ``category`` is not a string, or names no category
            in the library. The pre-6.4.0 names (``seasonal``, ``holiday``,
            ``mood``, ``ambient``, ``functional``, ``atmosphere``) are
            among the unrecognised: they were never a taxonomy this data
            carries, and the message lists the categories that exist.
    """
    if type(category) is not str:
        # derive_slug() would raise AttributeError on a non-string, which
        # contradicts the documented ValueError and reads as a library
        # bug rather than a bad argument.
        raise ValueError(
            f"Category must be a string, got {type(category).__name__}. "
            f"Available categories: {', '.join(cls.get_categories())}"
        )

    slugs = cls._slugs_for_category(derive_slug(category))
    if not slugs:
        raise ValueError(
            f"Category '{category}' is not recognised. "
            f"Available categories: {', '.join(cls.get_categories())}"
        )
    return {slug: cls.get(slug) for slug in sorted(slugs)}

Convenience Function

get_theme

get_theme(name: str) -> Theme

Get a theme by name.

Convenience function equivalent to ThemeLibrary.get(name).

PARAMETER DESCRIPTION
name

Theme name (case-insensitive)

TYPE: str

RETURNS DESCRIPTION
Theme

Theme object

Example
from lifx.theme import get_theme

evening = get_theme("evening")
await light.apply_theme(evening, power_on=True)
Source code in src/lifx/theme/library.py
def get_theme(name: str) -> Theme:
    """Get a theme by name.

    Convenience function equivalent to ThemeLibrary.get(name).

    Args:
        name: Theme name (case-insensitive)

    Returns:
        Theme object

    Example:
        ```python
        from lifx.theme import get_theme

        evening = get_theme("evening")
        await light.apply_theme(evening, power_on=True)
        ```
    """
    return ThemeLibrary.get(name)

Available Themes

The library carries 166 themes, resolvable under 168 names (the extra two are the forest and aurora_borealis rename aliases). 138 are captured from the LIFX app and carry the app's own display name and category; the remaining 28 have no app counterpart and sit under the Library category.

Rather than reproduce the inventory here — where it rots on every resync — ask the library:

from lifx import ThemeLibrary

names = ThemeLibrary.get_available_themes()   # every resolvable name

theme = ThemeLibrary.get("evening")
print(theme.slug, theme.name, theme.category)  # evening Evening Library

Each theme carries its ASCII slug, the app's name (which may contain spaces and punctuation) and its category. The categories and their theme counts are:

Category Themes
Archives 60
Library 28
Holidays 15
Music 14
Moods 13
Space 11
Art Series 10
Nature 8
Play 7

ThemeLibrary.get_by_category() takes the categories in the table above, matched case- and punctuation-insensitively (Art Series, art series and art_series all resolve), and ThemeLibrary.get_categories() lists them. The six pre-6.4.0 names (seasonal, holiday, mood, ambient, functional, atmosphere) are retired and raise ValueError listing the categories above — none of them mapped onto a single category, so see Theme Taxonomy Changes for what each one used to return. Theme.disposition and Theme.replaced_by record each theme's fate, documented on the same page.