Package detail

bs-css

reasonml-labs10.3kISC18.1.0

Css types

bucklescript, rescript, reasonML, css

readme

bs-css

Statically typed DSL for writing css in reason and rescript.

The bs-css library contains type css core definitions, it has different implementations:

  • bs-css-emotion is a typed interface to Emotion
  • bs-css-fela is a typed interface to Fela (react)
  • bs-css-dom is a typed interface to ReactDOMRe.Style.t (reason-react)

If you know another implementation that can be added, send a link in an issue or create a PR.

Installation

npm install --save bs-css bs-css-emotion
or
yarn add bs-css bs-css-emotion

In your bsconfig.json, include "bs-css" and "bs-css-emotion" in the bs-dependencies.

You can replace bs-css-emotion with bs-css-dom in the above instructions if you prefer to use React styles, or bs-css-fela for a different runtime.

Usage for bs-css-emotion

module Theme = {
  let basePadding = CssJs.px(5)
  let textColor = CssJs.black
}

module Styles = {
  /*
  Open the Css module, so we can access the style properties below without prefixing them with Css.
  You can use either Css or CssJs: Css module is using lists, CssJs is using arrays.
  If you're targeting js and/or using Rescript, prefer CssJs
 */
  open CssJs

  let card = style(. [
    display(flexBox),
    flexDirection(column),
    alignItems(stretch),
    backgroundColor(white),
    boxShadow(Shadow.box(~y=px(3), ~blur=px(5), rgba(0, 0, 0, #num(0.3)))),
    // You can add non-standard and other unsafe style declarations using the `unsafe` function, with strings as the two arguments
    unsafe("-webkit-overflow-scrolling", "touch"),
    // You can place all your theme styles in Theme.re and access as normal module
    padding(Theme.basePadding),
  ])

  let title = style(. [
    fontSize(rem(1.5)),
    lineHeight(#abs(1.25)),
    color(Theme.textColor),
    marginBottom(Theme.basePadding),
  ])

  let actionButton = disabled =>
    style(. [
      background(disabled ? darkgray : white),
      color(black),
      border(px(1), solid, black),
      borderRadius(px(3)),
    ])
}

@react.component
let make = () =>
  <div className=Styles.card>
    <h1 className=Styles.title> {React.string("Hello")} </h1>
    <button className={Styles.actionButton(false)} />
  </div>

Global css

You can define global css rules with global

open CssJs

global(. "body", [margin(px(0))])
global(. "h1, h2, h3", [color(rgb(33, 33, 33))])

Keyframes

Define animation keyframes;

open CssJs

let bounce = keyframes(. [
  (0, [transform(scale(0.1, 0.1)), opacity(0.0)]),
  (60, [transform(scale(1.2, 1.2)), opacity(1.0)]),
  (100, [transform(scale(1.0, 1.0)), opacity(1.0)]),
])

let styles = style(. [
  animationName(bounce),
  animationDuration(2000),
  width(px(50)),
  height(px(50)),
  backgroundColor(rgb(255, 0, 0)),
])

// ...
<div className=styles> {React.string("bounce!")} </div>

Css attributes

For some CSS parameters (like setting padding on an input field), one needs to use CSS attributes like so:

input[type="text"] {
   padding:20px;
}

The selector function can be used:

open CssJs
let styles = style(. [selector("input[type='text']", [padding(px(20))])])

Merging styles

You should avoid trying to merge styles in the same list of rules or by concatinating lists. A list of rules is converted into a JS object before being passed to Emotion where every property becomes a key in the object. This means you lose any earlier rule if you have another rule with the same property later in the list. This is especially noticable when writing sub-selectors and media queries

Trying to merge styles by just using concatenation can result in unexpected results.

This example:

open CssJs

let base = [
  padding(px(0)),
  fontSize(px(1))
]
let overrides = [
  padding(px(20)),
  fontSize(px(24)),
  color(blue)
]
let media1 = [
  media("(max-width: 768px)", [
    padding(px(10))
  ])
]
let media2 = [
  media("(max-width: 768px)", [
    fontSize(px(16)),
    color(red)
  ])
]
let mergedStyles = style(. Belt.Array.concatMany([base, overrides, media1, media2]))

generates the following:

.css-1nuk4bg {
  padding: 20px;
  font-size: 24px;
  color: #0000ff;
}
@media (max-width: 768px) {
  .css-1nuk4bg {
    font-size: 16px;
    color: #ff0000;
  }
}

As you can see both properties from base are overwritten (as opposed to overridden in css) and the media query in media1 is also lost because the media query from media2 overwrites it.

The merge method

merge safely merges styles by name. Uses Emotion’s cx method.

open CssJs

let mergedStyles = merge(. [
  style(. [padding(px(0)), fontSize(px(1))]),
  style(. [padding(px(20)), fontSize(px(24)), color(blue)]),
  style(. [media("(max-width: 768px)", [padding(px(10))])]),
  style(. [media("(max-width: 768px)", [fontSize(px(16)), color(red)])]),
])

Generates the following:

.css-q0lkhz {
  padding: 0px;
  font-size: 1px;
  padding: 20px;
  font-size: 24px;
  color: #0000ff;
}
@media (max-width: 768px) {
  .css-q0lkhz {
    padding: 10px;
  }
}
@media (max-width: 768px) {
  .css-q0lkhz {
    font-size: 16px;
    color: #ff0000;
  }
}

Nothing is lost and everything ends up in the final stylesheet where normal overrides apply.

Usage for bs-css-fela

First you need to use a provider in your Jsx:

let renderer = createRenderer()

switch ReactDOM.querySelector("#app") {
| None => ()
| Some(dom) =>
  ReactDOM.render(<CssReact.RendererProvider renderer> ... </CssReact.RendererProvider>, dom)
}

Then, you need to use the useFela hook in your Jsx:

module Styles = {
  /*
   Open the Css module, so we can access the style properties below without prefixing them with Css.
   You can use either Css or CssJs: Css module is using lists, CssJs is using arrays.
   If you're targeting js and/or using Rescript, prefer CssJs
  */
  open CssJs

  let card = style(. [
    display(flexBox),
    flexDirection(column),
    alignItems(stretch),
    backgroundColor(white),
    boxShadow(Shadow.box(~y=px(3), ~blur=px(5), rgba(0, 0, 0, #num(0.3)))),
    // You can add non-standard and other unsafe style declarations using the `unsafe` function, with strings as the two arguments
    unsafe("-webkit-overflow-scrolling", "touch"),
    // You can place all your theme styles in Theme.re and access as normal Reason module
    padding(Theme.basePadding),
  ])

  let title = style(. [
    fontSize(rem(1.5)),
    lineHeight(#abs(1.25)),
    color(Theme.textColor),
    marginBottom(Theme.basePadding),
  ])

  let actionButton = disabled =>
    style(. [
      background(disabled ? darkgray : white),
      color(black),
      border(px(1), solid, black),
      borderRadius(px(3)),
    ])
}

@react.component
let make = () => {
  let {css, _} = CssReact.useFela()

  <div className={css(. Styles.card)}>
    <h1 className={css(. Styles.title)}> {React.string("Hello")} </h1>
    <button className={css(. Styles.actionButton(false))} />
  </div>
}

Global css

You can define global css rules with global

open CssJs
let renderer = createRenderer()

renderGlobal(. renderer, "body", [margin(px(0))])
renderGlobal(. renderer, "h1, h2, h3", [color(rgb(33, 33, 33))])

Usage for bs-css-dom

Use style instead of classname, for example:

module Styles = {
  // Open the Css module, so we can access the style properties below without prefixing them with Css
  open CssJs

  let card = style(. [
    display(flexBox),
    flexDirection(column),
    alignItems(stretch),
    backgroundColor(white),
    boxShadow(Shadow.box(~y=px(3), ~blur=px(5), rgba(0, 0, 0, #num(0.3)))),
    // You can add non-standard and other unsafe style declarations using the `unsafe` function, with strings as the two arguments
    unsafe("-webkit-overflow-scrolling", "touch"),
    // You can place all your theme styles in Theme.re and access as normal Reason module
    padding(Theme.basePadding),
  ])

  let title = style(. [fontSize(rem(1.5)), color(Theme.textColor), marginBottom(Theme.basePadding)])

  let actionButton = disabled =>
    style(. [
      background(disabled ? darkgray : white),
      color(black),
      border(px(1), solid, black),
      borderRadius(px(3)),
    ])
}

@react.component
let make = () =>
  <div style=Styles.card>
    <h1 style=Styles.title> {React.string("Hello")} </h1>
    <button style={Styles.actionButton(false)} />
  </div>

Where is the documentation?

You can check out Css_Js_Core.rei and Css_Legacy_Core.rei.

Thanks

Thanks to emotion which is doing all the heavy lifting.

Thanks to bs-glamor which this repo was forked from.

Thanks to elm-css for dsl design inspiration.

changelog

Unreleased

18.1.0 - 2025-06-27

  • Implement colorScheme function
  • Add lightDark function to Color.t
  • Add Var in Color.t

bs-css-dom@6.1.0, bs-css-emotion@7.1.0, bs-css-fela@5.1.0

18.0.0 - 2024-06-27

  • BREAKING CHANGE - Fix spelling error with #substract - #278

  • Add min/max in PercentageLengthCalc

bs-css-dom@6.0.1, bs-css-emotion@7.0.1, bs-css-fela@5.0.1

17.0.2 - 2023-10-17

The calc function is now defined outside the Length type. If you find missing usages, enter a github issue.

  • BREAKING CHANGE - Reworked the calc function - #271
  • BREAKING CHANGE - Refactor time as Time, not int by @davesnx - #264

  • Add missing quotes to contentRule if not present - #263

bs-css-dom@5.0.2, bs-css-emotion@6.0.2, bs-css-fela@4.0.2

16.2.0 - 2023-05-03

bs-css-dom@4.2.0, bs-css-emotion@5.2.0, bs-css-fela@3.2.0

16.1.0 - 2023-05-03

  • Support size-adjust property for fontFace by @WhyThat - #267

bs-css-dom@4.1.0, bs-css-emotion@5.1.0, bs-css-fela@3.1.0

16.0.0 - 2023-01-20

  • BREAKING CHANGE - use an array for areas type
  • BREAKING CHANGE - rename Geomety to Geometry (missing r)

bs-css-dom@4.0.0, bs-css-emotion@5.0.0, bs-css-fela@3.0.0

15.3.1 - 2022-10-30

  • Convert bs-css files to Rescript - #260

bs-css-dom@3.3.1, bs-css-emotion@4.3.1, bs-css-fela@2.3.1

15.3.0 - 2022-05-18

bs-css-dom@3.3.0, bs-css-emotion@4.3.0, bs-css-fela@2.3.0

15.2.0 - 2022-03-14

bs-css-dom@3.2.0, bs-css-emotion@4.2.0, bs-css-fela@2.2.0

15.1.1 - 2022-03-02

bs-css-dom@3.1.1, bs-css-emotion@4.1.1, bs-css-fela@2.1.1

15.1.0 - 2022-01-20

  • Add new types (overscrollBehavior, overflowAnchor, maskPosition, maskImage) on behalf of Ahrefs - #251

bs-css-dom@3.1.0, bs-css-emotion@4.1.0, bs-css-fela@2.1.0

15.0.2 - 2021-12-06

  • Add scrollBehavior, columnWidth and caretColor by @Amirmoh10 - #249
  • Add gap (shortcut to gridGap), and gap2

bs-css-dom@3.0.2, bs-css-emotion@4.0.2, bs-css-fela@2.0.2

15.0.1 - 2021-08-26

  • BREAKING CHANGE - use rescript and @rescript/react
  • BREAKING CHANGE - for better consistency in CssJs, use array instead of list in gradient definition
  • BREAKING CHANGE - for better consistency in CssJs, uncurry selector and media functions - #244

  • Fix global injection in legacy mode - #238

  • Add borderRadius4 by @davesnx - #247
  • Add `start in TextAlign by @davesnx - #246

bs-css-dom@3.0.1, bs-css-emotion@4.0.1, bs-css-fela@2.0.1

14.0.2 - 2021-05-03

bs-css-dom@2.5.2, bs-css-fela@1.0.2

bs-css-emotion@3.0.0

  • BREAKING CHANGE bs-css-emotion: update to emotion v11 by @TomiS - #229

14.0.1 - 2021-03-30

  • Fix test helper dependency - #237

bs-css-dom@2.5.1, bs-css-emotion@2.5.1, bs-css-fela@1.0.1

14.0.0 - 2021-03-29

  • BREAKING CHANGE bs-css: updated the Css_Core API to accept renderer. it should have no impact on existing implementations.
  • Added bs-css-fela with a dependency to the fela library

bs-css-dom@2.5.0, bs-css-emotion@2.5.0, bs-css-fela@1.0.0

13.4.1 - 2021-03-19

  • Move react dependency out of bs-css-emotion
  • Use peer dependencies in bs-css-dom
  • Fix broken link to rei (doc) by @abenoit - #234
  • Add support for focus-visible pseudo-class by @TomiS - #233

bs-css-dom@2.4.1, bs-css-emotion@2.4.1

13.4.0 - 2021-02-20

bs-css-dom@2.4.0, bs-css-emotion@2.4.0

13.3.0 - 2021-01-26

  • Add stroke-dasharray for SVG by @pzshine

bs-css-dom@2.3.0, bs-css-emotion@2.3.0

13.2.0 - 2020-11-20

bs-css-dom@2.2.0, bs-css-emotion@2.2.0

13.1.0 - 2020-10-09

Added

  • New CssJs module that uses arrays instead of list for styles, more friendly to js and Rescript. This is not a breaking change, to use that new code you need to import CssJs instead of Css.

13.0.0 - 2020-08-26

Added

Breaking change

  • Update rgba/hsl/hsla signatures - #194
  • Update SVG fill property - #193
  • Delete the empty rule, it was just a shortcut to [] and was conflicting with the empty selector

bs-css-dom@2.0.0, bs-css-emotion@2.0.0

12.2.0 - 2020-04-14

bs-css-dom@1.2.0, bs-css-emotion@1.2.0

12.1.0 - 2020-03-20

12.0.1 - 2020-03-04

This release introduce 2 new packages: bs-css-emotion and bs-css-dom. You need to replace bs-css with bs-css-emotion (or bs-css-dom) in your package.json, and you need to add either bs-css-emotion or bs-css-dom in your bsconfig.json.

  • Add cursor aliases - #179
  • Add objectPosition by @damianfral - #177
  • Add support for fontDisplay in fontFace by @bloodyowl - #176
  • Fix typo string of `easeIn by @zalcode - #175
  • github repository moved to reasonml-labs organisation

Breaking change

  • Redesign content rule to support all possible values by @erykpiast - #180
  • FontFamily accepts cascading and predefined generic font names by @erykpiast - #171

    if you have code like this: fontFamily("Helvetica, sans-serif") you need to convert it to: fontFamilies([`custom("Helvetica"), `sansSerif])

Changed

  • Add upper bound to union types in Css.rei by @tomiS - #172

11.0.0 - 2019-11-12

Major release because of the breaking change in background-position definition, and some elements removed.

Changed

  • Extend rule to use gridLength type by @lukiwolski - #168
  • background-position definition by @lucasweng - #167
  • Rename Css_Types to Css_AtomicTypes

Removed

  • noContent selector, duplicate of empty
  • type aliases

10.0.1 - 2019-09-23

Added

  • textDecorationLine

Changed

  • Pseudo classes selectors have been updated and documented
  • Update properties : backgroundAttachment, backgroundClip, backgroundOrigin, backgroundRepeat, textOverflow, textDecorationLine, textDecorationStyle, width, wordWrap

10.0.0 - 2019-09-04

Major release because of the breaking change in shadows definition.

Breaking change

  • Css.rule is now an abstract type #153
  • Update shadow definition #148

boxShadow has been changed to be a rule to allow for none, important and all other rule related functions.

It means that the shadow properties must be updated to the following patterns:

// before:
boxShadow(~x=px(1), ~y=px(2));
boxShadows([boxShadow(yellow), boxShadow(red)]);
textShadow(~y=px(3), ~blur=px(2), black);
textShadows([textShadow(~y=px(3), ~blur=px(2), black), textShadow(~x=px(3), green)]);

// after:
boxShadow(Shadow.box(~x=px(1), ~y=px(2)));
boxShadows([Shadow.box(yellow), Shadow.box(red)]);
textShadow(Shadow.text(~y=px(3), ~blur=px(2), black));
textShadows([Shadow.text(~y=px(3), ~blur=px(2), black), Shadow.text(~x=px(3), green)]);

Added

  • Support for for object-fit property by @kuy - #125
  • Add fit-content option for width property by @mwarni - #149
  • Add support for grid-template-areas and grid-area by @drew887 - #156

Fixed

  • BoxShadow: none doesn't work - #148
  • !important doesn't apply to boxShadow - #147

Changed

  • Move types to Css.Types, updated some css properties
  • Use yarn instead of npm

9.0.1 - 2019-07-01

This is a major release: bs-css now depend on emotion 10.x instead of 9.x (see #114).

  • :bug: #139 - Units for hsl(a)
  • :bug: #138 - (top, bottom, left, right) properties should also belong to the 'cascading' type
  • :bug: #133 - Support pixel units for gradient color stops
  • :rocket: #131 - Support for "direction" property
  • :bug: #109 - flexShrink and flex should accept a float instead of a int

contributors: @simonkberg, @JakubMarkiewicz, @remitbri, @lucasweng

8.0.4 - 2019-04-04

  • :rocket: #125 - Add textShadows
  • :house: webpack is replaced by parcel, zero config local server

contributors: @Freddy03h

8.0.3 - 2019-03-20

  • :rocket: #118 - Minmax should allow fr
  • :rocket: Add a toJson function to convert a list of rules to a json object

contributors: @JakubMarkiewicz

8.0.2 - 2019-02-25

  • :rocket: #119 - Missing resize attribute
  • :rocket: #117 - Add spaceEvenly for justifyContent and alignContent
  • :rocket: #113 - Minmax in grid implementation

contributors: @JakubMarkiewicz, @lucasweng, @wegry

8.0.1 - 2019-01-31

  • :bug: #108 - Wrong value for minWidth/maxWidth
  • :rocket: #111 - Fill all allowed display values

contributors: @sean-clayton, @c19

8.0.0 - 2018-12-18

  • :rocket: #76 - Add support for commonly used name for font weight
  • :bug: #86 - Losing styles when merging nested selectors
  • :bug: #105 - flexGrow should accept a float instead of a int
  • :house: #95 - Remove unnecessary reverse of styles before passing to JS obj
  • :house: #91 - Swapped glamor runtime to emotion runtime

Breaking changes

This version of bs-css is using a new runtime library that replaces glamor.

This decision is driven by the following points:

  • the author of glamor is no more maintaining its project, the last commit happened more than one year ago,
  • emotion is the recommended alternative because it mostly matches glamor's API
  • better grid support : glamor uses a lot of the old -ms-grid and -webkit-grid prefixes (with IE11 support)
  • Merging styles with sub selectors work as expected and fix a bug bs-css had

Given that it is a major version, we also changed some functions in the API, especially regarding merging.

Migration

  • fontWeight number must now use the num constructor: you must change fontWeight(300) to fontWeight(num(300)). The benefit is that you can transform your absolute numbers to font names, see https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight#Common_weight_name_mapping.
  • merge was really a concat function and could lead to problems as seen in #86. This is why it has been changed to use the internal library (emotion) and its signature is now : list(string) => string. If you want to keep the same functionality than before, you can use List.concat instead or @ operator.
  • if you are mixin reason/js, you need to change your dependency and replace glamor with emotion
  • flexGrow accepts a float instead of an int, add a . to your int value (for ex, convert 1 to 1.)

Contributors

Big thanks to @baldurh and @wegry for their work on emotion.