Skip to main content

Easily define conditional class attributes for HTML elements.

Project description

clsx

Easily define conditional class attributes for HTML elements.

Motivation

Defining conditional class attributes based on a state can be cumbersome and error-prone with string concatenation. clsx provides a utility function to handle this logic efficiently, bringing the popular idea from the JavaScript ecosystem to the Python ecosystem.

Installation

The package is available on PyPI and can be installed using pip.

pip install clsx

Usage

The clsx function takes any number of arguments and returns a string that can be used as the value of the class attribute of an HTML element.

from clsx import clsx

clsx()                                        # ""
clsx("")                                      # ""
clsx("foo")                                   # "foo"
clsx("foo foo")                               # "foo"
clsx("foo \n\t foo")                          # "foo"

clsx("foo", None)                             # "foo"
clsx("foo", None, "bar")                      # "foo bar"

clsx("foo", None, "bar", None)                # "foo bar"
clsx("foo", None, "bar", None, "baz")         # "foo bar baz"

clsx("foo", False)                            # ""
clsx("foo", True)                             # "foo"

clsx("foo", "bar")                            # "foo bar"
clsx("foo", "bar", "baz")                     # "foo bar baz"
clsx("foo", "bar", "baz", "qux")              # "foo bar baz qux"

clsx(["foo", "bar"])                          # "foo bar"
clsx(["foo", "bar"], ["baz", "qux"])          # "foo bar baz qux"
clsx([("foo", True), ("foo", "bar", "baz")])  # "foo bar baz"

clsx(["foo foo"], "foo", "bar")               # "foo bar"
clsx("foo", ["foo", "bar", "baz"])            # "foo bar baz"

clsx({"foo": True, "bar": False})             # "foo"
clsx({"foo": True, "bar": True})              # "foo bar"
clsx({(lambda: "foo"): True})                 # "foo"

clsx(lambda: [lambda: ("foo", True)])         # "foo"

Low-level API

The core of the implementation is the methods in the ExpressionEvaluator class. For each type of input, there is a corresponding method that evaluates the input and returns an iterator of class names. These methods are designed to be chained together to form the final iterable.

For optimal performance and memory consumption, ExpressionEvaluator consists of several generator methods that yield the class names one by one, as they are consumed. This allows the evaluation process to be done lazily, which is especially useful when dealing with large/infinite and/or nested inputs. Since the evaluation is done lazily, the both the CPU and memory usage are kept to a minimum.

from clsx.evaluation import ExpressionEvaluator

output_stream = ExpressionEvaluator.evaluate(input_stream)

Modularity

The implementation supports on-the-fly modifications of class names out of the box by its nature. For more complex use cases similar to deduplication, CSS Modules resolution or anything else, the low-level API can be extended by chaining the output iterable with other generator functions. This allows for a modular design where each step of the evaluation process can be customized and extended independently, with zero extra overhead.

For an example of how to extend the low-level API, see the deduplication section below.

Deduplication

The deduplication is always enabled when using the high-level API. However, when using the low-level API, the deduplication must be done manually. This can be achieved by using the dedup function from the clsx.contrib.itertools module, which is a generator function that deduplicates the input stream without ahead of planning or buffering. See the example below.

from clsx.contrib.itertools import dedup
from clsx.evaluation import ExpressionEvaluator

output_stream = ExpressionEvaluator.evaluate(input_stream)
deduplicated_output_stream = dedup(output_stream)

CSS Modules

The CSS Modules resolution is not supported out of the box, but can be achieved by modifying the output stream by chaining it with a custom generator function that resolves the class names. See the example below.

from clsx.abc import ClassNameIterable
from clsx.evaluation import ExpressionEvaluator

STYLES = {"foo": "Zm9v"}

def resolve_css_module_class_names(
    iterable: ClassNameIterable,
) -> ClassNameIterable:
    for class_name in iterable:
        yield STYLES.get(class_name, class_name)

output_stream = ExpressionEvaluator.evaluate(input_stream)
resolved_output_stream = resolve_css_module_class_names(output_stream)

Inspiration

This project is inspired by the popular idea implemented and widely used in the JavaScript ecosystem. The name clsx is a positive nod to the project clsx by @lukeed, which provides a similar functionality for JavaScript.

The name clsx in this project is used as a shorthand for "class expression" and it might be pronounced as "classics".

License

This project is licensed under the MIT License.

See the LICENSE file for more information.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

clsx-0.1.1.tar.gz (11.3 kB view hashes)

Uploaded Source

Built Distribution

clsx-0.1.1-py3-none-any.whl (19.0 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page