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.

HTML
<a class="btn">
    Button
</a>
SCSS
@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%);
    }
}
Result

You can create multiple components at once by passing a comma-separated list to the component mixin.

SCSS
@include component(section, hero) {
    padding: 32px;
}
Compiles to
.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.

HTML
<a class="btn --action">
    Register Now
</a>
SCSS
@include component(btn) {
    ...

    @include option(action) {
        background-color: #a7e040;
        color: #1f1f1f;

        &:hover {
            background-color: darken(#a7e040, 10%);
        }
    }
}
Result

You can apply multiple options at once by passing a comma-separated list to the option mixin.

SCSS
@include component(alert) {
    ...

    @include option(danger, info) {
        color: #fff;
    }
}
Compiles to
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.

HTML
<a class="btn --action --has-icon">
    <i class="btn.icon"></i>
    Register Now
</a>
SCSS
@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;
            }
        }
    }
}
Result

You can create multiple parts at once by passing a comma-separated list to the part mixin.

SCSS
@include component(alert) {
    ...

    @include part(icn, content) {
        padding: 8px;
    }
}
Compiles to
.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.

HTML
<p>
    <span class="label +rounded">label</span>
</p>

<a class="btn --action --has-icon +rounded">
    <i class="btn.icon"></i>
    Register Now
</a>
SCSS
@include component(label) {
    padding: 2px 4px;
    background-color: #f92672;
    color: #fff;
    text-transform: uppercase;
}

@include tweak(rounded) {
    border-radius: 4px;
    overflow: hidden;
}
Result

Locations

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.

HTML
<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>
SCSS
@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;
        }
    }
}
Result
Please fix the errors before submitting the form.
Please enter a valid email address.

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.

SCSS
@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);
}
Compiles to
.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.

SCSS
@include component(btn) {
    @include option(action) {
        background-color: #a7e040;
    }

    @include important {
        border-radius: 0;
    }
}

@include tweak(rounded) {
    border-radius: 4px;
}
Compiles to
/* 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.

SCSS
$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;
    }
}
Compiles to
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.

SCSS
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;
    }
}
Compiles to
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!