clean, simple styling for the web
csstyle is a modern approach for crafting beautifully maintainable stylesheets. Keeping CSS clean and organized is really hard. csstyle provides a higher-level abstraction for writing modular CSS. Written for Sass, it makes your CSS readable and semantic, generates your selectors for you, and automatically handles things like specificity and nesting.
csstyle makes your project's styling refreshingly consistent
Installation
Install csstyle via npm or yarn.
$ npm install --save-dev csstyle
$ yarn add --dev csstyle
Next, you need to add the app
id to your html
tag. You can use another id if you like, but you will need to configure this in your csstyle settings.
<!DOCTYPE html>
<html id="app" lang="en">
...
</html>
Lastly, you'll need to import csstyle in your main sass file.
@import '~csstyle/csstyle';
Now you're set and can start creating components with options & parts, adding in tweaks and locations as needed. Enjoy!
Settings
csstyle uses the following styling conventions to indicate what type of class is being applied to an element:
- Components: no prefix
- Options:
--
- Parts:
.
- Tweaks:
+
- Locations:
@
- Root id:
app
You can customize all of these by setting the following variables in your sass file.
$csstyle-component-symbol: '';
$csstyle-option-symbol: '--';
$csstyle-part-symbol: '.';
$csstyle-tweak-symbol: '+';
$csstyle-location-symbol: '@';
$csstyle-root-id: 'app';
csstyle automatically escapes characters as needed, so you don't have to escape any special characters yourself when setting these variables.
Components
With csstyle you organize your CSS as reusable components.
<a class="btn">
Button
</a>
@include component(btn) {
display: inline-block;
position: relative;
padding: .5em 1em;
color: #1f1f1f;
background-color: #6bd9ed;
transition: background-color .2s;
&:hover {
background-color: darken(#6bd9ed, 10%);
}
}
You can create multiple components at once by passing a comma-separated list to the component
mixin.
@include component(section, hero) {
padding: 32px;
}
.section, .hero {
padding: 32px;
}
Components are the primary building blocks of your project, the more you leverage them the better. csstyle's focus on components make it a perfect match for Web/Angular/Ember/React/Vue components.
Tip Create a separate file for each component to make them easy to find & edit.
Options
Make your components super flexible with options, passed as arguments to the component. Options override the component's default styling.
<a class="btn --action">
Register Now
</a>
@include component(btn) {
...
@include option(action) {
background-color: #a7e040;
color: #1f1f1f;
&:hover {
background-color: darken(#a7e040, 10%);
}
}
}
You can apply multiple options at once by passing a comma-separated list to the option
mixin.
@include component(alert) {
...
@include option(danger, info) {
color: #fff;
}
}
html .alert.\--danger, html .alert.\--info {
color: #fff;
}
Options are a great way to organically grow your CSS over time. When you go to style something you can often just add a new option to an existing component. Code reuse FTW.
Parts
Components are composed of parts — child elements of the component that can be styled. Parts are very flexible. They can nest in other parts, react to component options, or even have their own dedicated options. Parts are applied using the component name followed by .
and the part name.
<a class="btn --action --has-icon">
<i class="btn.icon"></i>
Register Now
</a>
@include component(btn) {
...
@include option(has-icon) {
// padding-left: icon.width + padding-right;
padding-left: 1.5em + .5em * 2 + 1em;
}
@include part(icon) {
display: inline-block;
position: absolute;
top: 0;
bottom: 0;
left: 0;
// width: line-height + padding-top * 2;
width: 1.5em + .5em * 2;
padding-right: 4px;
background-color: rgba(#000, .1);
@include option(lower-right-pencil) {
&:before {
content: '\270E';
font-size: 1.5em;
line-height: 1.7;
}
}
}
}
You can create multiple parts at once by passing a comma-separated list to the part
mixin.
@include component(alert) {
...
@include part(icn, content) {
padding: 8px;
}
}
.alert\.icn, .alert\.content {
padding: 8px;
}
Parts make it easy to break complex components up into manageable chunks.
Tweaks
Create generic style tweaks that can be applied to any component, part, element, etc. Tweaks are applied using a +
sign followed by the tweak name. Tweaks automatically override the styling of components, options, and parts so you never have to add extra nesting or use the dreaded !important
flag just to apply a style exception.
As with components, you can create multiple tweaks at once by passing a comma-separated list to the tweak
mixin.
<p>
<span class="label +rounded">label</span>
</p>
<a class="btn --action --has-icon +rounded">
<i class="btn.icon"></i>
Register Now
</a>
@include component(label) {
padding: 2px 4px;
background-color: #f92672;
color: #fff;
text-transform: uppercase;
}
@include tweak(rounded) {
border-radius: 4px;
overflow: hidden;
}
label
Register NowLocations
Projects occasionally need to override styles in specific locations e.g. just on the home page or just in the chat feature. Locations are applied using an @
sign followed by the location name.
As with components, you can create multiple locations at once by passing a comma-separated list to the location
mixin.
<form class="form @has-errors">
<div class="alert --danger +hidden">
Please fix the errors before submitting the form.
</div>
<div class="form.group @has-error">
<label for="email">Email address</label>
<input type="email" class="form.control" id="email"
placeholder="jane@example.com">
<div class="form.feedback">
Please enter a valid email address.
</div>
</div>
</form>
@include tweak(hidden) {
display: none;
}
@include location(has-errors) {
@include tweak(hidden) {
display: block;
}
}
@include location(has-error) {
label {
color: #dc3545;
}
@include component(form) {
@include part(feedback) {
color: #dc3545;
}
@include part(control) {
border-color: #dc3545;
}
}
}
Policies
Policies are new in csstyle 2, and are a convenient way to automatically apply certain styles to any components, parts, options, tweaks, locations, or even your own css selectors, without having to create a separate class for them. In the background, csstyle simply creates a placeholder class that gets extended whenever the policy is applied. Policies are a handy tool to avoid code repetition.
There are two ways to create policies. You can use the dedicated add-policy
mixin, or use the policy
mixin and pass true
to the second parameter. To apply a policy, simply use the policy
mixin and pass the policy name.
@include add-policy(margin) {
&:first-child {
margin-top: 0;
}
&:last-child {
margin-bottom: 0;
}
}
@include policy(padding, true) {
&:first-child {
padding-top: 0;
}
&:last-child {
padding-bottom: 0;
}
}
@include component(section) {
margin: 32px 0;
@include policy(margin);
}
@include component(nav) {
@include part(item) {
margin: 8px 0;
padding: 4px;
border-left: 1px solid;
@include policy(margin);
@include policy(padding);
}
}
p {
padding: 16px 0;
@include policy(padding);
}
.section {
margin: 32px 0;
}
.nav\.item {
margin: 8px 0;
padding: 4px;
border-left: 1px solid;
}
p {
padding: 16px 0;
}
/* Margin policy */
.section:first-child, .nav\.item:first-child {
margin-top: 0;
}
.section:last-child, .nav\.item:last-child {
margin-bottom: 0;
}
/* Padding policy */
p:first-child, .nav\.item:first-child {
padding-top: 0;
}
p:last-child, .nav\.item:last-child {
padding-bottom: 0;
}
Tip You don't have to worry about accidentally overwriting placeholder classes, csstyle prefixes the placeholder classes for policies automatically with
%csstyle-policy-
.
Specificity
csstyle automatically handles specificity for you where necessary. It does this by nesting the compiled selectors. Options are automatically nested in the html
selector, while tweaks
are automatically nested in the #app
selector (where app
is the value of $csstyle-root-id
). If the compiled selector contains both a tweak and an option, it will nest it inside html#app
.
New in csstyle 2 is the important
mixin. If there is ever a reason you want to increase the specificty of certain styles, you can use the important
mixin. Any styles contained in its body will be nested inside an html#app
selector.
In the example below, we can ensure our btn
component always has a border-radius
of 0
, even if the rounded
tweak is applied.
@include component(btn) {
@include option(action) {
background-color: #a7e040;
}
@include important {
border-radius: 0;
}
}
@include tweak(rounded) {
border-radius: 4px;
}
/* Specificity: 1, 1, 1 */
html#app .btn {
border-radius: 0;
}
/* Specificity: 0, 2, 1 */
html .btn.\--action {
background-color: #a7e040;
}
/* Specificity: 1, 1, 0 */
#app .\+rounded {
border-radius: 4px;
}
Changing settings on the fly
You can change any of the csstyle settings on the fly if you need to, and csstyle even includes a handy mixin, reset-csstyle
, to restore to the original or default settings.
$csstyle-option-symbol: '~';
@import '~csstyle/csstyle';
// Change setting on the fly
$csstyle-option-symbol: ':';
$csstyle-component-symbol: 'c-';
@include component(btn) {
@include option(primary) {
background: pink;
}
}
// Reset to original settings
@include reset-csstyle;
@include component(btn) {
@include option(secondary) {
background: blue;
}
}
// Reset to default settings
@include reset-csstyle(true);
@include component(btn) {
@include option(info) {
background: lightblue;
}
}
html .c-btn.\:primary {
background: pink;
}
html .btn.\~secondary {
background: pink;
}
html .btn.\--info {
background: lightblue;
}
Manually Creating Selectors
You can manually generate selectors for components, parts, options, tweaks and locations if you ever need them. You can then use interpolation to use them. This is done by using their respective functions.
component()
: Prefixes the given string with a.
and the escaped component symbol.part()
: Prefixes the given string with the escaped part symbol.option()
: Prefixes the given string with a.
and the escaped option symbol.tweak()
: Prefixes the given string with a.
and the escaped tweak symbol.location()
: Prefixes the given string with a.
and the escaped location symbol.
Manually generating selectors can be useful for creating complex selectors that csstyle can't generate, using csstyle selectors outside their required parents, or extending csstyle selectors.
p {
margin: 16px 0;
&#{option(first)} {
margin-top: 0;
}
&#{option(last)} {
margin-bottom: 0;
}
}
@include tweak(shadow) {
box-shadow: 0 2px 5px 0 rgba(#000, .16), 0 2px 10px 0 rgba(#000, .12);
&:not(#{component(nav)}#{option(fixed)}) {
z-index: 5;
}
}
p {
margin: 16px 0;
}
p.\--first {
margin-top: 0;
}
p.\--last {
margin-bottom: 0;
}
#app .\+shadow {
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .16), 0 2px 10px 0 rgba(0, 0, 0, .12);
}
#app .\+shadow:not(.nav.\--fixed) {
z-index: 5;
}
Note Bear in mind that manually generating selectors does not deal with specificty or nesting!