Introduction

Rosebox is a CSS-in-Typescript library that provides features like strong types, typed functions (e.g., linearGradient), additional expressive shorthand properties(e.g., marginX, paddingX, an object-based syntax for the values of complex properties (e.g., animation), and support for high-quality IntelliSense.

It maps CSS's data-types into Typescript. Some of these data-types are mapped directly to TS's intrinsic types like stringLiteral, string, and number, while others are mapped to RB's custom-types, like Duration, TransformFunction and Length. It has been designed and developed with a focus on typo-reduction, better auto-completion, expressive API, and ease of data manipulation.

Typo-reduction and better auto-completion#

If you've done any development in React, then you've more likely than not used style-objects that have type React.CSSProperties. Let's put Rosebox.RBStyle and React.CSSProperties (v17.0.1) to a type-checking test:

const style: React.CSSProperties = {
display: 'ss', // Passes the type-checking
flexDirection: 'colum', // Error
alignContent: 'abc', // Passes the type-checking
justifyContent: 'Hey alignContent! we are siblings so I can get away too', // Passes the type-checking
fontSize: '16pxx', // Passes the type-checking
color: 'rgba(257, 255, 255)', // Passes the type-checking
lineHeight: 'We need a bit more of me between color and flexBasis', // Passes the type-checking
flexBasis: '100 % 100% yup', // Passes the type-checking
transform: 'trAnslate me or dIé tryıng', // Passes the type-checking
}
const styleRB: RBStyle = {
display: 'ss', // Error
flexDirection: 'colum', // Error
alignContent: 'abc', // Error
justifyContent: 'Hey alignContent! we are siblings so I can get away too', // Error
fontSize: '16pxx', // Error
color: 'rgba(257, 255, 255)', // Error
lineHeight: 'I feel sandwiched between color and display, I want more of me', // Error
flexBasis: '100 % 100% yup', // Error
transform: 'trAnslate me or dIé tryıng' // Error,
}

As you can see, the only type-error caught by the type system in the case of React.CSSProperties is the one related to flexDirection, but all the other properties pass the type-checking. Why? because all of them, except for flexDirection has the type string; the most permissive type in the universe.

With Rosebox, on the other hand, the type system catches the type-errors for all the properties. It's able to do so because properties in Rosebox are as fine-grained as they can be. They only get the string type as the last resort.

Here's a version of styleRB that would pass:

import {px, rgb, per, rotate, deg} from 'rosebox'
const styleRB: RBStyle = {
display: 'flex', // // PASS: the stringLiteral 'flex' is allowed
flexDirection: 'column', // PASS: the stringLiteral 'column' is allowed
alignContent: 'center', // PASS: the stringLiteral 'center' is allowed
justifyContent: 'center', // PASS: the stringLiteral 'center' is allowed
fontSize: px(16), // PASS: Type Length<LengthUnit> is allowed
color: rgb(255, 255, 255), // PASS: Type RGB is allowed
lineHeight: 1.45, // PASS: Type number is allowed
flexBasis: per(100), // PASS: Type Percentage is allowed
transform: rotate(deg(45)) // PASS: Type TransformFunction<'rotation'> is allowed,
}

Expressive API#

All unit-based types are implemented as generic types that take a type parameter specifying the type's unit. For example, Duration<'ms'> and Length<'em'>. This comes in handy when you're exposing an API, and you want to clearly state the unit of the value you're expecting. Consider the following naive react-component:

import React, { FC } from 'react'
import { Duration, sub, sub, ms } from 'rosebox'
type Props = {
inTime: Duration<'ms'>;
children: React.ReactNode;
};
const MyModal: FC<Props> = ({ inTime, children }) => {
const outTime = sub(inTime, ms(100)); // to make the transition faster on the way out
return; // UI stuff
};

Now, if we try to pass a Duration<'s'> we will get an error:

Error

Type 'Duration<"s">' is not assignable to type 'Duration<"ms">'

Of course, we don't always want to enforce a certain unit of Duration or any other unit-based type for that matter; in that case, we just omit the type parameter (e.g Duration).

Neat, right? But that's only one way in which RB provides an expressive API. Another one is providing an object-interface for some complex properties like animation.

Ease of data manipulation#

When manipulating colors, we usually need to convert them to string values before using them in our styles. Here's an example:

import { rgb } from 'color'
const baseBgColor = rgb(255, 255, 255)
const BgColorHovered = baseBgColor.lighten(0.5)
const StyleObject: React.CSSProperties = {
backgroundColor: BgColorHovered.string()
}

In RB, using @rosebox/colors, you're just dealing with RB-types and returning RB-types, so you don't need that extra serialization step which is handled internally:

import { rgb, RBStyle } from '@rosebox/core'
import { lighten } from '@rosebox/colors'
const baseBgColor = rgb(255, 255, 255)
const BgColorHovered = lighten(baseBgColor, 0.5)
const StyleObject: RBStyle = {
backgroundColor: BgColorHovered
}