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.
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>...</h1>
<p>...</p>
<a href="...">Read more</a>
</div>
Also the following rules will be added to the generated stylesheet: x
.el-teaser {
background: #888;
padding: 1em;
}
.el-teaser > h1 {
font-size: 2em
}
Since there are no styles specified for the a
and the p
element, the h1
is the only additional rule in this case. And as Teal exactly knows where the
H1 will end up in the DOM, it can use a very short, yet unique selector to
target it.
You can also think about a .tl
file as kind of custom HTML element with custom
attributes. If you use a tag name that starts with either /
, ./
or ../
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:
./two-cols {
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
}
}
}
Sometimes it can be hard for Teal to generate meaningful selectors:
div {
div {
float: left;
$left
}
div {
float: right;
$right
}
}
Teal could use nth-child()
to select the inner divs but in order to stay
compatible with IE < 9 Teal assigns synthetic classes instead, something like
.el-teaser > .div-1
. To help Teal generate better class names you can provide
naming hints like this:
div {
div#left {
...
}
div#right {
...
}
}
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);
}
You access @const
properties with const()
function like in this example:
div.error {
color: const(error);
}
With Teal you can define all media-specific styles right next to the rest of a component's style declarations:
a {
display: block;
@media $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.
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"));
}
var express = require('express')
var teal = require('teal-express')
var app = express()
var tl = teal(app)
app.get('/', function(req, res) {
res.render('page', { title: 'Hello world.' })
})
-
teal.tmLanguage for TextMate and Sublime Text
-
atom-teal for Atom
-
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