Mimcss Guide: Style Property Types
- Style Property Categories
- General Approach
- Numeric Types
- Working with Colors
- Complex Property Types
- Proxy Interfaces
As we already mentioned earlier, the Styleset
type, which is used to specify style property values in Mimcss, resembles the CSSStyleDeclaration
type in that it has properties with the same names: the camel-cased names of all CSS shorthand and longhand properties. The difference is that while the CSSStyleDeclaration
type defines all the properties to have the type string
, the Styleset
type defines a different type for each property with the goal of making it easier and less error-prone for developers to assign values to them. This unit describes the different types of the properties in the Styleset
type.
Style Property Categories
We can divide all the style properties into the following broad categories based on the values they accept:
- Keyword-based properties. These properties use keywords to define their values. An example is the
visibility
property, whose allowed values arevisible
orhidden
orcollapse
. - Number-based properties. These properties accept numbers usually (but not always) accompanied by a unit specification. An example is the
top
property with accepted values such as4px
or1.5em
or10%
. - Color properties. These properties accept named colors as well as color values specified in the hexadecimal form and as
rgb
andhsl
functions. - Complex value properties. These properties accept a list of values usually of different types and often in an arbitrary order. Shorthand properties usually belong to this category; for example, the
border
property. There are, however, longhand properties with complex values too, for example theclip-path
property. - Multi-value properties. These properties accept one or many values of either a simple or a complex type. For some properties, such as
animation
, the multiple values are separated by commas while for others, such aspadding
, the values are separated by spaces.
When deciding what type to use for each individual style property, in addition to the category Mimcss considered the following factors:
- There are many properties that do not belong strictly to one category. For example, the
vertical-align
property can be defined using a number of keyword values but it also accepts a numeric<length>
value. - All properties accept the standard keyword values
initial
,unset
,inherit
andrevert
. - All properties can accept the value of a custom CSS property using the
var()
CSS function. - Number-based properties must support the numeric functions, such as
min()
andcalc()
.
General Approach
The goal of Mimcss is to boost the developers’ productivity by increasing convenience and decreasing the number of errors. These two requirements are somewhat contradictory: the fastest and most convenient way to specify property values is to use strings but it is the most error-prone way too. The least error-prone way is to use the strictest types possible, but this is often inconvenient. Mimcss has to make a certain trade off balancing between the convenience and type strictness and here is the general approach that Mimcss has settled on:
-
For keyword-based properties, the property type is defined as a union of string literals. The
string
type is not part of type definition. For example, the type for thevisibility
property is defined as:export type Visibility_StyleType = "visible" | "hidden" | "collapse";
-
For number-based types (such as
<length>
,<angle>
, etc.) the type is defined as anumber
and a callable interface that is implemented by “unit functions” such ascss.percent(100)
orcss.inch(0.5)
. In addition, Mimcss allows a few string literals for such frequently used values as"100%"
and"1fr"
. For every numeric type, Mimcss defines a default unit to be used with integer numbers and another unit to be used with floating point numbers. For example, Mimcss defines theCssLength
type for values of the CSS<length>
type. When integer numbers are used for theCssLength
values, they are interpreted as pixels; that is, having the unit suffix of"px"
. For floating point numbers of theCssLength
type, the suffix is"em"
. -
For color properties, the
CssColor
type includes string literals for all the named colors. It also allows specifying colors as numbers and providescss.rgb()
andcss.hsl()
functions. Thestring
type is not part of theCssColor
type definition. -
For complex properties Mimcss uses arrays, tuples, functions and objects to provide the type-safe and convenient way to specify values. For most complex short-hand properties such as
background
,animation
, etc. object type with fields corresponding to the long-hand properties is allowed. Most fields in such object types are optional and thus can be omitted. -
The string literals
initial
,reset
,inherit
andrevert
are part of any property type definition. -
All properties accept a custom CSS property if it is defined for a compatible type; for example:
class MyStyles extends css.StyleDefinition { defaultColor = this.$var( "color", "red"); cls = this.$class({ // this will work because "backgroundColor" property is of the same type as "color" backgroundColor: this.defaultColor, // this will NOT compile because "width" property is of different type than "color" width: this.defaultColor }) }
-
All properties accept the result of the
css.raw()
function, which serves as an escape hatch when there is a need to specify a string value for a property that normally doesn’t accept thestring
type. Note that theraw()
function is a tag function and must be invoked with the template string without parentheses.
Numeric Types
CSS defines the <number>
type for unitless numbers and several numeric types which require unit specification, e.g. <length>
, <angle>
, <percent>
, etc. For each of these types, Mimcss defines a TypeScript type, which serves dual purpose:
- To allow for convenient numeric operations, and
- To distinguish between different types so that
<angle>
values would not be assigned to<length>
properties by accident.
Mimcss supports the following types:
CssNumber
. This type corresponds to the CSS<number>
type. This type doesn’t allow specifying values as strings - only as numbers. An example of a property that uses this type iswindows
.CssPercent
. This type corresponds to the CSS<percent>
type. This type allow specifying values as strings or as numbers. An example of a property that uses this type iszoom
.CssLength
. This type corresponds to the CSS<length> | <percent>
type. This type allow specifying values as strings or as numbers. Integer numbers will be considered as specifying thepx
units; floating numbers - theem
units. An example of a property that uses this type isleft
. There are many properties that use this type as part of their definition. For example, themargin
property can be specified either as oneCssLength
value or as an array of two to fourCssLength
values.CssAngle
. This type corresponds to the CSS<angle> | <percent>
type. This type allow specifying values as strings or as numbers. Integer numbers will be considered as specifying thedeg
units; floating numbers - theturn
units. An example of a property that uses this type isfont-style
.CssAngle
can also be used as a parameter to several transform, filter and image functions.CssTime
. This type corresponds to the CSS<time>
type. This type allow specifying values as strings or as numbers. Integer numbers will be considered as specifying thems
units; floating numbers - thes
units. An example of a property that uses this type istransition-duration
.CssResolution
. This type corresponds to the CSS<resolution>
type. This type allow specifying values as strings or as numbers. Integer numbers will be considered as specifying thedpi
units; floating numbers - thex
units. An example of a property that uses this type isresolution
property of a media query.CssFrequency
. This type corresponds to the CSS<frequency>
type. This type allow specifying values as strings or as numbers. Integer numbers will be considered as specifying theHz
units; floating numbers - thekHz
units.
For each of the above numeric types, Mimcss also provides an object that implements “mathematical” functions min()
, max()
, clamp()
and calc()
. These objects are:
Num
for working with theCssNumber
type.Len
for working with theCssLength
type.Angle
for working with theCssAngle
type.Time
for working with theCssTime
type.Resolution
for working with theCssResolution
type.Frequency
for working with theCssFrequency
type.
Mimcss also implements functions named after every unit allowed for the above CSS types. For example, the functions rem()
, inch()
and cm()
work with the <length>
CSS type. Similarly, the functions deg()
and rad()
work with the <angle>
CSS type. The types returned from these functions make it impossible to assign a value of the wrong type to a property.
class MyStyles extends css.StyleDefinition
{
// This will work because the 'cm()' function returns type compatible with the 'left' property
cls1 = this.$class({ left: css.cm(1.5) })
// This will NOT compile because the 'rad()' function returns type incompatible with the 'left' property
cls2 = this.$class({ left: css.rad(1) })
}
Mathematical Functions
CSS specifies functions min()
, max()
, clamp()
and calc()
for working with numeric values. The type-specific objects listed above implement these functions in a type safe manner.
The min()
and max()
functions accept a variable number of parameters of the corresponding numeric type. The return value can only be assigned to the property of the compatible type.
class MyStyles extends css.StyleDefinition
{
// This will work because the 'Len.min()' function returns type compatible with the 'left' property
cls1 = this.$class({ left: css.Len.min( 100, 10.5, css.inch(1.5), css.percent(50)) })
// This will NOT compile because the 'Angle.min()' function returns type incompatible with the 'left' property
cls2 = this.$class({ left: css.Angle.min( 45, 0.25, css.rad(1)) })
}
The clamp()
function accepts three parameters of the corresponding numeric type. The return value can only be assigned to the property of the compatible type.
class MyStyles extends css.StyleDefinition
{
// This will work because the 'Len.clamp()' function returns type compatible with the 'left' property
cls1 = this.$class({ left: css.Len.clamp( 100, css.inch(1.5), css.percent(50)) })
// This will NOT compile because the 'Angle.min()' function returns type incompatible with the 'left' property
cls2 = this.$class({ left: css.Angle.clamp( 45, 0.25, css.rad(1)) })
}
The calc()
function is a tag function accepting a template string with embedded parameters of the corresponding numeric type. The return value can only be assigned to the property of the compatible type.
class MyStyles extends css.StyleDefinition
{
// This will work because the 'Len.calc()' function returns type compatible with the 'left' property
cls1 = this.$class({ left: css.Len.calc`(100% - ${100} - ${css.Len.cm(2)}) / 2` })
// This will NOT work because the type of the second parameter is not compatible with the 'Len.calc()'
// function parameter type
cls2 = this.$class({ left: css.Len.calc`(100% - ${100} - ${css.Angle.deg(30)}) / 2` })
// This will NOT compile because the 'Angle.calc()' function returns type incompatible with the 'left' property
cls3 = this.$class({ left: css.Angle.calc`(100% - ${100} - ${css.Angle.deg(30)}) / 2` })
}
Working with Colors
Mimcss provides the CssColor
type for using with properties that accept colors either as their values or as parameters in arrays, objects and functions used to set their values. The CssColor
type is defined as a union of all named colors (keys of the INamedColors
interface), the built-in keyword values of transparent
and currentcolor
, and a number
type. Notably, the CssColor
value cannot be defined as an arbitrary string.
The number
type allows specifying color values using hexadecimal numbers, which resembles the CSS hexadecimal color notation. If in regular CSS, a color can be specified as #A1B2C3
, in Mimcss, the same value will be expressed as a number 0xA1B2C3
. The alpha channel value can be included just as in regular CSS: #A1B2C380
in CSS and 0xA1B2C380
in Mimcss. Note that Mimcss does not support the CSS 3-digit hexadecimal notation such as #CCC
- all numeric values are treated as having two hexadecimal digits per color separation.
Mimcss provides the Colors
object, which has properties named after every built-in color. The values of these properties are the numeric values of the corresponding colors; thus, using a color name such as red
is the same as using the Colors.red
property.
Mimcss implements the rgb()
and hsl()
functions, which have the same functionality that their CSS counterparts. These two functions accept the three separation values and an optional alpha channel percentage. In addition, the alpha()
function allows applying an alpha channel percentage to any color value including the named colors.
As a last resort - just as with any other style property - the raw()
function can be used if there is a need to specify the color value as a regular CSS string.
Here are the examples of using different ways of assigning color values:
class MyStyles extends css.StyleDefinition
{
// Named color
cls1 = this.$class({ color: "red" })
// Property of the Colors object
cls2 = this.$class({ color: css.Colors.red })
// Numeric value
cls3 = this.$class({ color: 0xFF0000 })
// rgb() function with alpha channel
cls4 = this.$class({ color: css.rgb( 255, 0, 0, 0.5) })
// alpha() function with named color
cls5 = this.$class({ color: css.alpha( "red", 0.5) })
// raw() function
cls6 = this.$class({ color: css.raw`#CCC` })
}
Custom Named Colors
Mimcss allows assigning names to your own color values and using them just as you use the built in color names. This can be useful for defining colors that are widely used in your application. The technique involves the TypeScript module augmentation mechanism and the Mimcss-provided Colors
object.
Let’s assume that you have a color value 0xA1B2C3
and you want to assign the name myFavColor
to it. The first step is to add this name to the INamedColors
interface as a property of the number
type:
declare module "mimcss"
{
interface INamedColors
{
myFavColor?: number;
}
}
The above code makes the TypeScript compiler believe that every object implementing the INamedColors
interface has the myFavColor
(optional) property; therefore, the compiler will allow us to write the following code:
class MyStyles extends css.StyleDefinition
{
cls = this.$class({ color: "myFavColor" })
}
The next step is to add the myFavColor
property to the Colors
object with the appropriate value:
css.Colors.myFavColor = 0xA1B2C3;
Now we can use the string "myFavColor"
(or its css.Colors.myFavColor
counterpart) in any place where color values are accepted.
Complex Property Types
There are quite a few CSS properties with complex property types, whose values involve multiple parts of different types. Mimcss strives to make assigning values to these properties as easy and type-safe as possible and employs all types available in TypeScript including tuples, arrays, objects and functions.
Note: we distinguish tuples from arrays in that tuple has a pre-defined number of elements, which can be of different types. TypeScript doesn’t have a special type for tuples, although it is possible to define a type as an array with a finite number of elements with different types. TypeScript will enforce element types for such arrays.
Mimcss defines a separate type for each complex property. The string
type is also part of type definition for these properties, so that developers can simply set property values as strings without the need to use the raw()
function. Note, however, that with the special property types, developers can leverage the full power of the TypeScript language as well as Mimcss features such as custom CSS properties, constants, etc.
The following list gives a brief description of the complex properties:
-
Properties like
padding
andmargin
that allow specifying one or more values of the<length> | <percentage>
CSS type. Mimcss defines the type of such properties asOneOrBox<CssLength>
. This allows specifying either a singleCssLength
value or a tuple of two to fourCssLength
values:class MyStyles extends css.StyleDefinition { // single raw string cls1 = this.$class({ margin: css.raw`4px 8px` }) // single number (all four sides wil be set to 8px) cls2 = this.$class({ margin: 8 }) // tuple with two elements - number cls3 = this.$class({ margin: [4, 1.2] }) // a single castom variable (4px for top, 0.5em for left and right, "auto" for bottom) defaultMargin = this.$var( "margin", [4, 0.5, "auto"]) cls4 = this.$class({ margin: this.defaultMargin }) // tuple with four elements mixing numbers, strings and custom variables defaultTopMargin = this.$var( "CssLength", 8) cls5 = this.$class({ margin: [this.defaultTopMargin, "auto", 0.5, 4] }) }
-
Properties like
animation
,background
,box-shadow
,font
,text-decoration
,text-shadow
,transition
. These are shorthand properties, with multiple elements of different types. For the majority of these properties, thestring
type is allowed. Mimcss also defines object types where the object’s fields correspond to the different parts of the CSS property value. For example, theanimation
property has the object type as part of its type definition that defines fieldsname
,duration
,func
,delay
,count
,direction
,mode
andstate
. All these fields are declared as optional; therefore, developers can only specify those that they need to.In CSS, in most cases, the order of the elements in the shorthand properties is not important; however, sometimes it is and that can create confusion and errors. For example, in the string for the
animation
property, the first time value is always the duration and the second is the delay. In Mimcss, with the object notation, there is no confusion because the field names unambiguously convey the meaning of the parameters. Object notation for the shorthand properties in some sence is similar to using longhand properties; however, it is different in two aspects:- It results in a shorthand property being used in the CSS rule, which resets the unspecified longhand properties.
- It is less verbose and thus more convenient.
Note that for every field in the object notation, developers can use not just constants, but any allowed method that results in the proper type including custom variables and references to other Mimcss objects. For example, for the
animation
property, the name of the animation is usually given as a reference to the previously defined @keyframes rule:class MyStyles extends css.StyleDefinition { move = this.$keyframes( [ [ "from", { top: 0 } ], [ 50, { top: 150 } ], [ "to", { top: "100%" } ] ]) defaultDuration = this.$var( "CssTime", 1200) cls = this.$class({ animation: { name: this.move, duration: this.defaultDuration } }) }
-
For properties using images such as
background-image
,list-style-image
,cursor
, etc., Mimcss provides implementations of the functions listed under the<image>
CSS type. This includesurl()
,gradient()
and their variants. -
For the
transform
property, Mimcss provides implementations of the functions listed under the<transform-function>
CSS type. This includesmatrix()
,perspective()
rotate()
,scale()
,skew()
,translate()
and their variants. -
For the
filter
andbackdrop-filter
properties, Mimcss provides implementations of the functions listed under the<filter-function>
CSS type. This includesblur()
,brightness()
contrast()
,dropShadow()
,grayscale()
,hueRotate()
,invert()
,opacity()
,saturate()
andsepia()
. -
For the
clip-path
,shape-outside
andoffset-path
properties, Mimcss provides implementations of the functions listed under the<basic-shape>
CSS type. This includesinset()
,circle()
ellipse()
,polygon()
andpath()
.
Proxy Interfaces
Mimcss provides a number of Proxy interfaces such as IFilterProxy
, ITransformProxy
, etc. These are callable interfaces, which means that entities that implement them are functions and not classes. All these interfaces derive from the IGenericProxy
interface, which is defined as follows:
export interface IGenericProxy<T extends string>
{
(p?: T): string;
}
The derived proxy interfaces are defined as follows:
export interface IFilterProxy extends IGenericProxy<"filter"> {}
export interface ITransformProxy extends IGenericProxy<"transform"> {}
The main purpose of the proxy interfaces is to provide type safety. Let’s take, for example, two style properties: filter
and transform
. The filter
property accepts strings according to the CSS <filter-function>
type, for example filter: blur(5px)
. The transform
property accepts strings according to the CSS <transform-function>
type, for example transform: scale(0.5)
.
What if a developer makes a mistake of incorrectly assigning the style properties in a regular CSS file? For example:
/* INCORRECT !!! */
.my-class {
filter: scale(0.5);
transform: blur(0.5);
}
The error will only be found at run time when the browser understands that the style values are invalid.
The goal of Mimcss is to catch errors like this at compile time and that’s where proxy interfaces come to help. Mimcss uses the IFilterProxy
interface as one of the types for the filter
property and the ITransformProxy
interface as one of the types for the transform
property. The blur()
and scale()
functions are declared as follows:
export function blur( radius: Extended<CssLength>): IFilterProxy
export function scale( cx: Extended<CssNumber>, sy?: Extended<CssNumber>): ITransformProxy
Since the IFilterProxy
and ITransformProxy
interfaces are incompatible due to the different values of the generic type, the following code will not compile:
class MyStyles extends StyleDefinition
{
myClass = this.$class({
filter: css.scale(0.5), // COMPILATION ERROR !!!
transform: css.blur(0.5) // COMPILATION ERROR !!!
})
}