MIXT documentation: Mixt CSS

Mixt CSS: tools to write CSS in python.

Introduction

The goal is to replace the need to use a CSS preprocessor by generating CSS via a real language (ie Python), so with the ability to have calculations, "mixins", etc done in real python.

And with a few tricks, it allows to write code that resemble a lot like normal CSS. For example you don't have to use strings for properties or values (only for selectors).

Almost everything in CSS is covered, with additions borrowed from sass and less:

  • nesting to avoid selectors repetition

  • units management (computations, calc)

  • multi-values properties

  • at-rules, with nested content (@media, @support) or not (@charset)

  • values overriding (think vendor-prefixes).

  • extends (like in sass)

  • mixins... because it's python and you can compose dicts how you want

Examples

Here is an example that resumes all the features:

>>> from mixt.contrib.css import load_css_keywords, css_vars, render_css
>>> # Must be done only once. This will load a big list of known CSS keywords and speed up
... # the resolution of the "undefined" variables in your css methods encapsuled by `@css_vars``.
... # It is not mandatory but will speed up a lot your css functions, in exchange of a few ms at
... # start time, and a few dozens of KB in memory (there is a lot of CSS keywords ;) )
... load_css_keywords()
>>> # The `@css_vars(globals())` decorator is mandatory to resolve "undefined" variables. They're
... # will only be a few of them if `load_css_keywords` is called before.
... # The usage of `globals()` is to put the existing and created CSS keywords in the current scope
... # so you can access them in the function without thinking about it.
... @css_vars(globals())
... def css():
...     return { # All must be in a dict.
...
...         # At-rule without content.
...         charset("UTF-8"): None,
...
...         # Html tag selector: no need for quotes
...         body: {
...
...             # No need for quotes for the property name
...             margin: 0,
...
...             # Units usage, could also be written as 2*em
...             # this gives: `padding: 2em`
...             padding: em(2),
...
...         },
...
...         header: {
...             # Need to encapsulate in parentheses: items are joined with a
...             # space, so this gives `margin: 1em 2em`
...             # it's a shortcut of the `join` function, so this could also
...             # be done like this: `margin: join(1*em, 2*em)`
...             margin: (1*em, 2*em),
...
...             # Encapsulation as a list is different than as a tuple (like just above)
...             # items are now joined with a comma, so this gives
...             # `font-family: Gill, Helvetica, sans-serif`
...             # it's a shortcut of the `many` function so tis could also
...             # be done like this: `font-family: many("Gill", "Helvetica", "sans-serif")`
...             font-family: ["Gill", "Helvetica", "sans-serif"],
...
...             # Here is a combination of `many` and `join`.
...             # It could also have been written this way, using explicitly `many` and `join`:
...             # text-shadow: many(
...             #     join(1*px, 1*px, 2*px, blue),
...             #     join(0, 0, 1*em, red),
...             #     join(0, 0, 0.2*em, red),
...             # ),
...             text-shadow: [
...                 (1*px, 1*px, 2*px, blue),
...                 (0, 0, 1*em, red),
...                 (0, 0, 0.2*em, red),
...             ],
...         },
...
...         nav: {
...             # Notice the use of `-`: `margin-top` is not a valid python identifier but
...             # an override of the "sub" operator between two strings.
...             # Also works, without quotes: `marginTop`, `margin_top`, `MarginTop`
...             margin-top: 1*em,
...
...             # The `ul` is nested under `nav`. For nested selectors which don't include the
...             # `&` character, the selector is automatically prefixed with `& ` (the space is
...             # important). So here, `ul` is in fact `& ul`, and `&` will be replaced by the
...             # chaining of the parent selectors, so at the end we'll have `nav ul`.
...             ul: {
...                 # `list` is usually a python builtin but as it's part of a css "keyword", it is
...                 # now a part of a CSS var. To access the `list` builtin, use the `builtins` or
...                 # `b` namespace: `b.list(thing_to_cast_list)`. Note that it is different for
...                 # python keywords, like `for` and `class` (but also non-keywords like `super`,
...                 # `self` and `cls`) that must, to be used as a CSS var, be prefixed with an
...                 # underscore `_` (or used with the first letter in uppercase): `super()` will
...                 # work for the python `super` pseudo-keyword, and `_super` or `Super` will wor
...                 # as a CSS var rendering "super".
...                 list-style: none,  # do not use the python `None` here, it won't work
...
...
...                 # Here we add the `&` ourselves. If we had only `:after`, it would have been
...                 # extended to `& :after` which is, in CSS, completely different than `&:after`.
...                 # Like before, the `&` is replaced by the chaining of the parent selectors, so
...                 # at the end we'll have `nav ul:after`
...                 "&:after": {
...                     # here we don't put anything, so at the end this selector won't be rendered
...                 },
...
...                 # There is nothing that force us to put the `&` at the beginning. Here at the
...                 # end will have `body.theme-red ul nav`.
...                 "body.theme-red &": {
...                     background: red,
...
...                     # and here it will be `body.theme-red nav ul li`
...                     li: {
...                         color: white,
...                     },
...
...                     # You can put comments in the generated CSS. The key must start with `/*`.
...                     "/*": "this is a comment",
...
...                     # If you want many comments at the same level, still start the key with
...                     #  `/*` but complete it, like we did here with another `*`.
...                     # Also note how we can handle a multi-lines comment.
...                     #
...                     "/**": '''this is a
...                               multi-lines comment''',  # number of spaces it not important
...
...                     # If you don't want to bother with the different keys, you can use the
...                     # `comment()` function that will produce a different string key each time.
...                     comment(): "another comment",
...
...                 },
...
...                 li: {
...                     # Here we have a simple calculation, this will render `1.5em` in the
...                     # final CSS.
...                     # It is especially useful if one of the value is a variable previously
...                     # defined like: `min_height = 1*em` and then `height: min_height + 0.5*em`
...                     height: 1*em + 0.5*em,
...
...                     # Here we have a complex calculation that will result in a `calc` call in
...                     # CSS: `width: calc(100% - 2em)`.
...                     # Also note the use of `pc` for "percent", as we cannot use `%` alone
...                     # in python. `percent` is also available.
...                     width: 100*pc - 2*em,
...
...                     # Here we have a media query, which is for `nav ul li`, and every selector
...                     # inside will behave like before: `& ` will be prepended if `&` is not
...                     # present, and `&` will be replaced by the chaining of the parent selectors.
...                     # Notice how we use `&` instead of `and` in the media query condition, and
...                     # a dict. This will translate to `@media screen and (max-width: 800px)`.
...                     media(screen & {max-width: 800*px}): {
...
...                         # Properties directly in a `media` (or any other "at-rule") dict are for
...                         # the selector just before, so here it is for `nav ul li`.
...                         width: 5*em,
...
...                         # And here it is for `nav ul li b`, still for the media query we defined
...                         # We have to quote "b" because it's a shortcut for the `builtins` module
...                         "b": {
...                             background: white,
...                         },
...
...                         # Here the use of `combine` is not needed but it shows how you
...                         # can pass many dicts (not limited to 2) for a selector.
...                         # If no dicts share the same keys, a new dict will be returned, else
...                         # it's a special object that will hold the dicts that will be rendered
...                         # in order. It is useful if you want to compose dicts on the fly, or
...                         # you want to use some "mixins"
...                         a: combine(
...                             {background: white},
...                             {text-decoration: underline}
...                         )
...                     }
...                 }
...             }
...
...         },
...         # Here is another CSS entry for the `header`. We cannot have twice the same key in a
...         # python dict but we can trick it by adding starting/ending spaces that will be removed
...         # when generating the CSS. It can be particularly handy for overriding stuff while
...         # taking advantage of the cascading feature of CSS. Yes we don't need this here, but
...         # it's for the example
...         "header ": {
...
...             # To use vendor-prefixed properties, we could have done the same, using `background`
...             # with one more space each time. Or we can use the `override` function that make
...             # this more clear and simple.
...             # This will produce:
...             # header {
...             #     background: -webkit-linear-gradient(left, blue, red, blue);
...             #     background: -moz-linear-gradient(left, blue, red, blue);
...             #     background: -ms-linear-gradient(left, blue, red, blue);
...             #     background: -o-linear-gradient(left, blue, red, blue);
...             #     background: linear-gradient(left, blue, red, blue);
...             # }
...
...             background: override(
...                 -webkit-linear-gradient(left, blue, red, blue),
...                 -moz-linear-gradient(left, blue, red, blue),
...                 -ms-linear-gradient(left, blue, red, blue),
...                 -o-linear-gradient(left, blue, red, blue),
...                 linear-gradient(left, blue, red, blue),
...             )
...         },
...
...         "body.theme-red": {
...
...             # When you want to have some properties for multiple selectors at once, you
...             # can pass them as a tuple (or a generator, but not a list because a list cannot
...             # be accepted as a dict key). Here the final selector will be:
...             # `body.theme-red p, body.theme-red blockquote`.
...             (p, blockquote): {
...                 border: (solid, red, 1*px),
...
...                 # But you can also pass them as a string in a form of a comma separated list.
...                 # And if the parent is also a multiple-selector, nesting is managed as it
...                 # should.
...                 # Here the final selector will be:
...                 # `body.theme-red p strong, body.theme-red p em,
...                 # body.theme-red blockquote strong, body.theme-red blockquote em`.
...                 "strong, em": {
...                     color: red,
...                 },
...
...                 # Like always, `& ` is prepended to each selector and replaced, but you can put
...                 # it where you want.
...                 # Here the final selector will be:
...                 # `body.theme-red p:target, body.theme-red p:focus,
...                 # body.theme-red blockquote:target, # body.theme-red blockquote:focus`.
...                 "&:target, &:focus": {
...                     border-width: 3*px,
...                 }
...             }
...         },
...
...         # Keys that starts with `%` are meant to be used with `extend`, like below. The key will
...         # be replaced by all the selectors that extend it. If no selector use it, it won't be
...         # rendered
...         "%box": {
...             border: (solid, black, 1*px),
...             # A css to extend can be nested like a regular css.
...             # If ".message" extend "box", we'll have two rules: ".message" and ".message a"
...             a: {
...                 text-decoration: underline,
...             }
...         },
...
...         # This is how we use `extend` by assing the name (without `%`). Instead of a name it can
...         # also be a dict defined directly or as a variable (useful for storing extends in a
...         # "library")
...         ".message": extend("box"),
...
...         # An extend can accept a named `css`. Here, ".message-important" will be used for the
...         # "box" but also as its own rule with the content of the `css` argument.
...         ".message-important": extend("box", css={
...             border-width: 2*px,
...         }),
...
...         # An extend can extend another one
...         "%abs-box": extend("box", css={
...             position: absolute,
...         }),
...
...         # Here, we extend two things. First, the "box", as seen before, then, a dict. Notice we
...         # don't use the `css` argument, so it's an extend. There is no limit on the number of
...         # things we can extend.
...         ".alert": extend("abs-box", {z-index: 1000}),
...
...         # As we extend the same dict as before, the two selectors ".alert" and ".popup" will be
...         # used for the same rule like this: `.alert, .popup: { z-index: 1000 }`
...         ".popup": extend({z-index: 1000}, "abs-box"),
...
...         # You can include "raw" CSS by using the `:raw:` key, or any key starting with `:raw:`.
...         # (because as always you cannot have twice the same key in a python dict).
...         # It can be handy to import CSS generated/copied/whatever from elsewhere.
...         # Note that it's "raw" CSS so there is no nesting with parent selectors, but it will
...         # still be indented to match the current rendering mode.
...         ":raw:": ".foo: { color: blue; }",
...         ":raw::": ".bar { color: white; }",
...
...         # If you don't want to bother with the different keys, you can use, like for comments,
...         # the `raw()` function that will produce a different string key each time.
...         raw(): ".baz { color: red; }"
...     }
>>> # Now we can render this css
... print(render_css(css()))
@charset 'UTF-8';
body {
  margin: 0;
  padding: 2em;
}
header {
  margin: 1em 2em;
  font-family: Gill, Helvetica, sans-serif;
  text-shadow: 1px 1px 2px blue, 0 0 1em red, 0 0 0.2em red;
}
nav {
  margin-top: 1em;
}
nav ul {
  list-style: none;
}
body.theme-red nav ul {
  background: red;
}
body.theme-red nav ul li {
  color: white;
}
/* this is a comment */
/* this is a
   multi-lines comment */
/* another comment */
nav ul li {
  height: 1.5em;
  width: calc(100% - 2em);
}
@media screen and (max-width: 800px) {
  nav ul li {
    width: 5em;
  }
  nav ul li b {
    background: white;
  }
  nav ul li a {
    background: white;
    text-decoration: underline;
  }
}
header {
  background: -webkit-linear-gradient(left, blue, red, blue);
  background: -moz-linear-gradient(left, blue, red, blue);
  background: -ms-linear-gradient(left, blue, red, blue);
  background: -o-linear-gradient(left, blue, red, blue);
  background: linear-gradient(left, blue, red, blue);
}
body.theme-red p, body.theme-red blockquote {
  border: solid red 1px;
}
body.theme-red p strong, body.theme-red p em, body.theme-red blockquote strong,
body.theme-red blockquote em {
  color: red;
}
body.theme-red p:target, body.theme-red p:focus, body.theme-red blockquote:target,
body.theme-red blockquote:focus {
  border-width: 3px;
}
.message, .message-important, .alert, .popup {
  border: solid black 1px;
}
.message a, .message-important a, .alert a, .popup a {
  text-decoration: underline;
}
.message-important {
  border-width: 2px;
}
.alert, .popup {
  position: absolute;
}
.alert, .popup {
  z-index: 1000;
}
.foo: { color: blue; }
.bar { color: white; }
.baz { color: red; }

Rendering modes

render_css can render CSS in different ways. The choices are provided by Modes, to import from mixt.contrib.css:

Modes

Rendering modes for render_css.

Example

>>> css = {
...     ".content": {
...         "color": "blue",
...         "font-weight": "bold",
...         "background": "green",
...         ".foo": {
...             "color": "green",
...         },
...         "@media(all and (max-width: 600px)": {
...             "": {
...                 "color": "red",
...                 "/*": "a comment",
...                 "font-weight": "normal",
...                 ".foo": {
...                     "color": "yellow",
...                 }
...             }
...         },
...         ":raw:": ".foo-bar {color: black}",
...         ".bar": {
...             "color": "orange",
...         },
...         "z-index": 1,
...     },
... }

>>> from mixt.contrib.css import Modes, render_css
>>> print(render_css(css, Modes.COMPRESSED))
.content{color:blue;font-weight:bold;background:green}.content .foo{color:green}@media(all and (
max-width: 600px){.content{color:red;font-weight:normal}.content .foo{color:yellow}}.foo-bar {
color: black}.content .bar{color:orange}.content{z-index:1}

>>> print(render_css(css, Modes.COMPACT))
.content {color: blue; font-weight: bold; background: green}
.content .foo {color: green}
@media(all and (max-width: 600px) {
 .content {color: red; font-weight: normal}
 .content .foo {color: yellow}
}
.foo-bar {color: black}
.content .bar {color: orange}
.content {z-index: 1}

>>> print(render_css(css, Modes.NORMAL))
.content {
  color: blue;
  font-weight: bold;
  background: green;
}
.content .foo {
  color: green;
}
@media(all and (max-width: 600px) {
  .content {
    color: red;
    /* a comment */
    font-weight: normal;
  }
  .content .foo {
    color: yellow;
  }
}
.foo-bar {color: black}
.content .bar {
  color: orange;
}
.content {
  z-index: 1;
}

>>> print(render_css(css, Modes.INDENT))
.content {
    color: blue;
    font-weight: bold;
    background: green;
}

    .content .foo {
        color: green;
    }

    @media(all and (max-width: 600px) {

        .content {
            color: red;
            /* a comment */
            font-weight: normal;
        }

            .content .foo {
                color: yellow;
            }
    }

    .foo-bar {color: black}

    .content .bar {
        color: orange;
    }

.content {
    z-index: 1;
}

>>> print(render_css(css, Modes.INDENT2))
.content {
        color: blue;
        font-weight: bold;
        background: green;
    }

    .content .foo {
            color: green;
        }

    @media(all and (max-width: 600px) {

        .content {
                color: red;
                /* a comment */
                font-weight: normal;
            }

            .content .foo {
                    color: yellow;
                }
        }

    .foo-bar {color: black}

    .content .bar {
            color: orange;
        }

.content {
        z-index: 1;
    }

>>> print(render_css(css, Modes.INDENT3))
.content {
    color: blue;
    font-weight: bold;
    background: green }

    .content .foo {
        color: green }

    @media(all and (max-width: 600px) {

        .content {
            color: red;
            /* a comment */
            font-weight: normal }

            .content .foo {
                color: yellow } }

    .foo-bar {color: black}

    .content .bar {
        color: orange }

.content {
    z-index: 1 }

Attributes

COMPRESSED: Dict = <Modes.COMPRESSED: {'indent': '', 'endline': '', 'sel_after_endline': '', 'decl_endline': '', 'indent_closing_incr': 0, 'decl_incr': 0, 'space': '', 'opening_endline': '', 'closing_endline': '', 'indent_children': False, 'force_indent_rule_children': '', 'last_semi': False, 'display_comments': False}>

The minimal mode, reduces the white space at the minimum, on one line. Comments are not rendered.

COMPACT: Dict = <Modes.COMPACT: {'indent': '', 'endline': '', 'sel_after_endline': '\n', 'decl_endline': ' ', 'indent_closing_incr': 0, 'decl_incr': 0, 'space': ' ', 'opening_endline': '', 'closing_endline': '', 'indent_children': False, 'force_indent_rule_children': ' ', 'last_semi': False, 'display_comments': False}>

Render each selector on its own line, without indentation except for @ rules content. Comments are not rendered.

NORMAL: Dict = <Modes.NORMAL: {'indent': ' ', 'endline': '\n', 'sel_after_endline': '\n', 'decl_endline': '\n', 'indent_closing_incr': 0, 'decl_incr': 1, 'space': ' ', 'opening_endline': '\n', 'closing_endline': '\n', 'indent_children': False, 'force_indent_rule_children': '', 'last_semi': True, 'display_comments': True}>

Each selector and each declaration is on its own line. Declarations are indented. Selectors are not, except in @ rules. Comments are rendered.

INDENT: Dict = <Modes.INDENT: {'indent': ' ', 'endline': '\n', 'sel_after_endline': '\n\n', 'decl_endline': '\n', 'indent_closing_incr': 0, 'decl_incr': 1, 'space': ' ', 'opening_endline': '\n', 'closing_endline': '\n', 'indent_children': True, 'force_indent_rule_children': '', 'last_semi': True, 'display_comments': True}>

Same as NORMAL but with each "sub" selector indented from is parent (".foo bar" is indented one more level than ".foo").

INDENT2: Dict = <Modes.INDENT2: {'indent': ' ', 'endline': '\n', 'sel_after_endline': '\n\n', 'decl_endline': '\n', 'indent_closing_incr': 1, 'decl_incr': 2, 'space': ' ', 'opening_endline': '\n', 'closing_endline': '\n', 'indent_children': True, 'force_indent_rule_children': '', 'last_semi': True, 'display_comments': True}>

Same as INDENT but declarations are indented twice, and closing } is indented once, to be at the same level as the sub selectors.

INDENT3: Dict = <Modes.INDENT3: {'indent': ' ', 'endline': '\n', 'sel_after_endline': '\n\n', 'decl_endline': '\n', 'indent_closing_incr': -100, 'decl_incr': 1, 'space': ' ', 'opening_endline': '\n', 'closing_endline': ' ', 'indent_children': True, 'force_indent_rule_children': '', 'last_semi': False, 'display_comments': True}>

Same as INDENT but closing } are put at the end of the previous declaration.

Functions

set_default_mode(mode: <enum Modes>) → None

Change the default CSS rendering mode.

Details
Arguments
mode: <enum Modes>

The rendering mode to use.

Returns
None
Example
>>> from mixt.contrib.css import Modes, set_default_mode, get_default_mode
>>> get_default_mode().name
'NORMAL'
>>> set_default_mode(Modes.COMPACT)
>>> get_default_mode().name
'COMPACT'

override_default_mode(mode: <enum Modes>) → Any

Create a context manager to change the default rendering mode in a with block.

Details
Arguments
mode: <enum Modes>

The rendering mode to use in the with block.

Returns
Any
Example
>>> from mixt.contrib.css import Modes, override_default_mode, get_default_mode
>>> get_default_mode().name
'NORMAL'
>>> with override_default_mode(Modes.COMPACT):
...     print(get_default_mode().name)
...     with override_default_mode(Modes.INDENT3):
...         print(get_default_mode().name)
...     print(get_default_mode().name)
... print(get_default_mode().name)
COMPACT
INDENT3
COMPACT
NORMAL

get_default_mode() → <enum Modes>

Return the actual default rendering mode.

Details
Returns
<enum Modes>

The actual default rendering mode.

Example
>>> from mixt.contrib.css import Modes, set_default_mode, get_default_mode
>>> get_default_mode().name
'NORMAL'
>>> set_default_mode(Modes.COMPACT)
>>> get_default_mode().name
'COMPACT'

Special vars

Here is a list of special vars that are available in functions decorated by css_vars(globals()).

Except for dummy and builtins, they are normal CSS vars when used normally, ie they simply render their own name, but they have a special behaviour when called.

join(*values: Any) → str

Allow to have css shortcut by joining them with spaces.

Details

Note that in render_css, if a tuple is encountered, it will be converted as a Join so using (solid, blue, 1*px) will be the same as join(solid, blue, 1*px).

Arguments

*values: Any

The values to join with a space.

Returns

str

The new composed string.

Example

>>> from mixt.contrib.css.vars import join
>>> border = Var("border")
>>> color, style = "red", "solid"
>>> {border: join(color, style, "1px")}
{'border': 'red solid 1px'}
>>> {border: (color, style, "1px")}
{'border': 'red solid 1px'}

many(*values: Any) → str

Allow to have multiple css values by joining them with commas.

Details

Note that in render_css, if a list is encountered, it will be converted as a Many so using ["foo", "bar", "baz"] will be the same as many("foo", "bar" "baz").

Arguments

*values: Any

The values to join with a comma.

Returns

str

The new composed string.

Example

>>> from mixt.contrib.css.units import Unit
>>> from mixt.contrib.css.vars import join, many
>>> color1, color2 = "red", "blue"
>>> text, shadow = Var.many("text shadow")
>>> px, em = Unit.many("px em")
>>> {text-shadow: many(
...     join(1*px, 1*px, 2*px, color1),
...     join(0, 0, 1*em, color2),
...     join(0, 0, 0.2*em, color2),
... )}
{'text-shadow': '1px 1px 2px red, 0 0 1em blue, 0 0 0.2em blue'}
>>> {text-shadow: [
...     (1*px, 1*px, 2*px, color1),
...     (0, 0, 1*em, color2),
...     (0, 0, 0.2*em, color2),
... ]}
{'text-shadow': '1px 1px 2px red, 0 0 1em blue, 0 0 0.2em blue'}

override(*declarations: Any) → Override

Allow to have a css key defined multiple times.

Details

Arguments

*declarations: Any

The different declarations to set for a key.

Returns

Override

A new instance of Override with filled declarations.

Example

>>> from mixt.contrib.css import render_css
>>> from mixt.contrib.css.vars import override
>>> col1, col2 = "blue", "red"
>>> background, webkit, moz, ms, o, linear, gradient, left = \
... Var.many("background webkit moz ms o linear gradient left")
>>> render_css({background: override(
...     -webkit-linear-gradient(left, col1, col2, col1),
...     -moz-linear-gradient(left, col1, col2, col1),
...     -ms-linear-gradient(left, col1, col2, col1),
...     -o-linear-gradient(left, col1, col2, col1),
...     linear-gradient(left, col1, col2, col1),
... )})
{
background: -webkit-linear-gradient(left, blue, red, blue);
background: -moz-linear-gradient(left, blue, red, blue);
background: -ms-linear-gradient(left, blue, red, blue);
background: -o-linear-gradient(left, blue, red, blue);
background: linear-gradient(left, blue, red, blue);
}

extend(*extends: Union[str, Dict[str, Any]], css: Union[Dict[str, Any], None] = None) → Extend

Allow to have a css key defined multiple times.

Details

Arguments

*extends: Union[str, Dict[str, Any]]

The list of names or dicts to extend.

css: Union[Dict[str, Any], None] = None

If set, some CSS to add to the selector. None by default.

Returns

Extend

A new instance of Extend with filled extends.

Example

>>> from mixt.contrib.css import render_css
>>> from mixt.contrib.css.vars import extend
>>> render_css({
...     "%box": {"border": "solid red 1px"},
...     ".foo": extend("box"),
...     ".bar": extend("box", css={"color": "black"}),
... })
.foo, .bar {
  border: solid red 1px;
}
.bar {
  color: black;
}

combine(*dicts: Union[Dict[str, Any], Combine, str]) → Union[Combine, CssDict]

Allow to define a css pseudo "dict" from many dicts.

Details

Arguments

*dicts: Union[Dict[str, Any], Combine, str]

The different dicts to combine. Can be a dict or already a Combine that will be expanded to its own dicts, or a string, in which case it will be converted to a raw CSS entry (using raw() as key)

Returns

Union[Combine, CssDict]

If no dicts have a shared key, will return a new CssDict with all keys, else new instance of Combine with with the given dicts

Example

>>> from mixt.contrib.css.vars import combine, Combine
>>> css = combine({"foo": 1}, {"bar": 2})
>>> isinstance(css, Combine)
False
>>> isinstance(css, dict)
True
>>> css
{'foo': 1, 'bar': 2}
>>> css = combine({"foo": 1}, {"foo": 2})
>>> isinstance(css, Combine)
True
>>> isinstance(css, dict)
False
>>> css.dicts
[{'foo': 1}, {'foo': 2}]

raw() → Var

Generate a new unique key starting with :raw:, to be used as a CSS key.

Details

When encountering this key, the rendered will render the value untouched., outside of any selector.

Alias: r()

Returns

Var

A new generated string with a unique key.

comment() → str

Generate a new unique key starting with /*, to be used as a CSS key.

Details

When encountering this key, the rendered will encapsulate the value in /* */ and will render it.

Alias: c()

Returns

str

A new generated string with a unique key.

string(value: Any) → str

Allow to have a quoted string in css. For example for the content attribute.

Details

Aliases: str, repr

Arguments

value: Any

The value to quote. Note that the result is a call to repr(str(value)).

Returns

str

The quoted string

Example

>>> from mixt.contrib.css.vars import string
>>> string("foo")
"'foo'"

>>> from mixt.contrib.css import render_css
>>> content = Var("content")
>>> print(
...     render_css({
...         '.foo:after': {
...             content: string("foo")
...         }
...     })
... )
.foo:after {
  content: 'foo';
}

Not(*args: Any) → Var

Allow to negate (using not) the given arguments.

Details

Arguments

*args: Any

The args to be negated. If more than one, they will be surrounded by parentheses.

Returns

Var

The newly created Var.

Example

>>> from mixt.contrib.css.vars import Not
>>> Not("foo")
'not foo'
>>> Not("foo", "bar")
'not (foo or bar)'

merge(*dicts: Union[Dict, Combine]) → CssDict

Merge many dictionaries into one, recursively.

Details

For keys that have dict as values, they are also merged. For values other than dicts, the last defined wins.

Arguments

*dicts: Union[Dict, Combine]

The different dicts (or instances of Combine) to join.

Returns

CssDict

The joined dicts in a CssDict (subclass of dict)

Raises

If at least one of dicts is not a dict or an instance of Combine.

Example

>>> from mixt.contrib.css.vars import merge
>>> merge({
...    "foo": {"a": 1, "b": 2, "c": 3},
...    "bar": {"A": 11}
... }, {
...     "foo": {"b": 20, "c": None, "d": 4},
...     "baz": {"ZZ": 22},
... })
{
    'foo': {'a': 1, 'b': 20, 'd': 4},
    'bar': {'A': 11},
    'baz': {'ZZ': 22}
}

dummy

Special "empty" var.

Details

To use to force things to behave like a Var.

Alias: _

Example

>>> from mixt.contrib.css.vars import dummy as _
>>> _
''
>>> _ + "foo"
'+foo'
>>> _("foo")
'(foo)'
>>> _ & {"foo": "bar"}
'(foo: bar)'

builtins

Make available python builtins that may have been replaced by css vars.

Details

Alias: b

Example

>>> from mixt.contrib.css import css_vars, render_css, load_css_keywords

>>> load_css_keywords()

>>> @css_vars(globals())
>>> def css():
...
...     b.print("foo")  # the real python `print`
...
...     return {
...         ".bar": {bar: print("bar")},  # not the python `print`
...     }

>>> print(render_css(css()))
foo
.bar {
  bar: print(bar);
}

At-rules

At-rules are special vars but when called, are converted to CSS at-rules, ie rules starting with a @.

Example

>>> from mixt.contrib.css import css_vars, render_css, load_css_keywords

>>> load_css_keywords()

>>> @css_vars(globals())
>>> def css():
...     return {
...         ".foo": {
...             width: 5*em,
...             media(screen & {max-width: 600*px}): {
...                 width: 3*em,
...             }
...         }
...     }

>>> print(render_css(css()))
.foo {
  width: 5em;
}
@media screen and (max-width: 600px) {
  .foo {
    width: 3em;
  }
}

Rules

  • charset(foo): @charset 'foo'

  • _import(foo): @import foo

  • namespace(foo): @namespace foo

  • media(foo): @media foo

  • supports(foo): @supports foo

  • document(foo): @document foo

  • page(foo): @page foo

  • font_face(foo): @font-face foo

  • keyframes(foo): @keyframes foo

  • viewport(foo): @viewport foo

  • counter_style(foo): @counter-style foo

  • font_feature_values(foo): @font-feature-values foo

  • swash(foo): @swash foo

  • annotation(foo): @annotation foo

  • ornaments(foo): @ornaments foo

  • stylistic(foo): @stylistic foo

  • styleset(foo): @styleset foo

  • character_variant(foo): @character-variant foo

Usage with CSS collector

mixt.contrib.css can be used on its own but it is totally possible to use it with the mixt CSS collector.

In fact, it uses it all the times even if the collected CSS are just strings.

To have your render_css_global or render_css method be able to return a CSS dict, you cannot return directly a dict because it is used to handle namespaces.

So you have two 0ptions:

  • Convert the dict to a CssDict

  • Return a call to combine

The first is useful when you have a simple CssDict: return CssDict({foo: bar}).

The second can serve the exact same purpose: return combine({foo: bar}), but is also useful to combine many dicts: return combine({foo: bar}, call_to_function_that_return_a_dict()).

And of course don't forget to decorate your method.

Here is an example:

from mixt.contrib.css import css_vars

class MyComponent(Element):

    @css_vars(globals())
    @classmethod
    def render_css_global(cls, context):
        return CssDict({
            ".foo": {
                color: white;
            }
        })

Don't forget to call load_css_keywords(), for example in the __init__.py file of your components directory.

Note that calls to extend will work between components, as soon as the name of the extend is defined before.

You can do this with a "Css library" component like we do in the following example to defined a ext named extend, that is used in our components. This work because we include the CssLib component in the app.

class CssLib(Element):
    @classmethod
    def render_css_global(cls, context):
        return CssDict({
            "%ext": {"ext": "end"}
        })

class Foo(Element):
    @classmethod
    def render_css_global(cls, context):
        return CssDict({
            ".foo": extend("ext", css={
                "color": "FOO",
            })
        })

class Bar(Element):
    @classmethod
    def render_css_global(cls, context):
        return CssDict({
            ".bar": extend("ext", css={
                "color": "BAR",
            })
        })

class App(Element):
    def render(self, context):
        return <CSSCollector render_position="before">
            <CssLib />
            <Foo />
            <Bar />
        </CSSCollector>

print(str(App())
<style type="text/css">
.foo, .bar {
  ext: end;
}
.foo {
  color: FOO;
}
.bar {
  color: BAR;
}
</style>

This of course can also be done without the CssLib component as you can directly use dicts when calling extend:

# this could be in an other python files, available for all your components
extends = {
    "ext": {"ext": "end"}
}

class Foo(Element):
    @classmethod
    def render_css_global(cls, context):
        return CssDict({
            ".foo": extend(extends["ext"], css={
                "color": "FOO",
            })
        })

class Bar(Element):
    @classmethod
    def render_css_global(cls, context):
        return CssDict({
            ".bar": extend(extends["ext"], css={
                "color": "BAR",
            })
        })

class App(Element):
    def render(self, context):
        return <CSSCollector render_position="before">
            <Foo />
            <Bar />
        </CSSCollector>

print(str(App())
<style type="text/css">
.foo, .bar {
  ext: end;
}
.foo {
  color: FOO;
}
.bar {
  color: BAR;
}
</style>