teal

stylesheets without selectors

Teal – Stylesheets without selectors

Teal is a new take on CSS that throws away the concept of selectors to make it easy to reason about the styles that get actually applied.

With Teal, stylesheets turn into an implementation detail: It feels as if you were writing inline styles (just with a much nicer syntax) and Teal converts them into rules and class names for you.

This allows you to split your whole frontend into separate components that are guaranteed to be free of side effects.

Unlike preprocessors which which only look at one side (the CSS), teal also addresses the other part: the generation of markup.

You define markup and style together in one place (one .tl file for each component) and teal figures out the appropriate CSS rules and class names in a BEM/SMACSS-like fashion.

In other words Teal transforms .tl files into a stylesheet and a bunch of templates. By default these templates are compiled to JavaScript (supporting both Node.JS and browsers) but you can also plug in other language adapters to compile them to PHP or whatever runtime you would like to use.

The teal-react add-on for example compiles .tl files into React components.

Syntax

A Teal file looks a lot like LESS or SCSS at the first glance – except that it also contains placeholders which tell Teal where content should be placed:

Here is a simple example, lets call it el/Teaser.tl:

div {
  background: #888;
  padding: 1em;
  h1 {
    font-size: 2em;
    $title
  }
  p {
    $children
  }
  a {
    href = $link
    "Read more"
  }
}

This, when rendered, will generate the following HTML structure:

<div class="el-Teaser">
  <h1 class="el-Teaser__h1">...</h1>
  <p>...</p>
  <a href="...">Read more</a>
</div>

Also the following rules will be added to the generated stylesheet:

.el-Teaser {
  background: #888;
  padding: 1em;
}
.el-Teaser__h1 {
  font-size: 2em
}

Since there are no styles specified for the <a> and the <p> no class names are assigned to these elements.

You can think of a .tl file as kind of custom HTML element with custom attributes. If you use a tag name that contains a slash, Teal will interpret it as path, resolve it and render the specified file:

div {
  /el/Teaser {
    title = "Hello world"
    children = "Lorem ipsum"
  }
  ./Foo {
    title = "Another component"
  }
}

Note: You can not only pass primitive values as attributes but also other elements or document fragments:

./TwoCols {
  left = /el/Teaser { title = "Left" }
  right = /el/Teaser { title = "Right" }
}

If you pass children to a component Teal will expose them as $children. So the following two examples are equivalent:

/el/Teaser {
  children = {
    "Hello" b { "World!" }
  }
}
// this can be written as:
 
/el/Teaser {
  "Hello" b { "World!" }
}

If a component does not contain a $children variable all nested content is appended to the component's root element. All other unknown parameters are set as HTML attributes. This allows you to style HTML elements without having to list all possible attributes. See how the following example does neither contain $children nor $href:

a {
  text-decoration: none;
  color: inherit;
  :hover {
    color: teal;
  }
}

A component may define different states (aka modifiers):

button {
  background: gray;
  .primary {
    background: blue;
    font-size: 2em;
  }
  .danger {
    background: red;
  }
}

To activate a state just pass a truthy parameter with the name of the state:

./Button { primary=true }

Note: If you omit the value and just specify a name true is implied. Hence the following code yields the same result:

./Button { primary }

If multiple elements inside a component need to be styled when a certain state is active, just repeat the same modifier and Teal will figure out the appropriate selectors:

button {
  .primary {
    background: blue;
  }
  i {
    .primary {
      color: green
    }
  }
}

You can define constants for colors or sizes using the @const directive. You just have to make sure that the .tl file in which the constants are defined is processed before the files in which they are used. The easiest way to guarantee this is to put @const into a file placed in Teal's root directory.

@const {
  error: #f00;
  gutter: 12px;
  desktop: (min-width:960px);
}

With Teal you can define all media-specific styles right next to the rest of a component's style declarations:

a {
  display: block;
  @media const(desktop) {
    float: left;
    width: 50%;
  }
}

When building the CSS, Teal will collect all identical media queries and group them in one single @media block at the end of the stylesheet.

By default Teal creates class names according to the following rules:

  • The file name is converted into PascalCase, e.g. .SampleComponent
  • Folder names are converted to camelCase and slashes are converted into dashes, e.g. /foo/bar/sample-component.tl gets .foo-bar-SampleComponent
  • Descendants are prefixed with the component's class followed by an underscore, e.g. .foo-Teaser_foo-Image
    • Anonymous elements get an extra underscore, e.g. .foo-Teaser__h1
    • If there is more than one matching element a number is appended, e.g. .foo-Teaser__h1-2
  • States

If a component uses an external asset, Teal resolves the path relative to the .tl file and (if used with express) exposes it. This is done by using the built-in src() function:

div {
  img { src=src("./rainbow.gif") }
  background: url(src("./sky.jpg"));
}

Usage

var express = require('express')
var teal = require('teal-express')
 
var app = express()
var tl = teal(app)
 
app.get('/', function(reqres) {
  res.render('page', { title: 'Hello world.' })
})

Syntax Highlighting

Add-Ons

  • teal-browserify to use Teal components in browser apps and/or to easily create browserify bundles from within a .tl file.

  • teal-watch to live-reload the HTML/CSS when a file is modified

  • teal-autoprefixer to automatically add vendor prefixes

  • teal-react to compile .tl files into React components