2
PhrameModular, configurable SASS framework mostly to be used with CSS modules.
2
offers:
What Phrame- 0kb footprint
- Multilevel configuration system
- Config references
- Color/Scheme management
- Exposed breakpoint rules
- Easing library
- Helper mixins
- Modules
- No bloat
- Use on existing codebase
Table of contents
- Basic setup
- Config system
- Config Management
- Mixins
- Modules
- Q&A
- Final words
Basic setup
npm install phrame2 --save
Now use it:
// Load the environment (has not output)
@import '~phrame2/core';
// Just a simple reset (optional)
@import '~phrame2/module/reset';
// Some basic styles (optional)
@import '~phrame2/module/basic';
// Expose breakpoints (optional)
@import '~phrame2/module/expose';
Phrame
2
related stuff (mixins, functions, etc.) are prefixed with an_
so Phrame2
won't conflict your codebase.
Config system
The config system uses Sass Maps as a multi dimensional array - since Sass is only able to handle a single level (looking at you map-get).
With Phrame2
my primary goal was to build a flexible tool to handle multiple
sites/schemes/styles/layouts from one codebase. For this to work I needed a system where it's simple to override the common stuff
and makes easy to switch color schemes for example. To achieve this I've built a flow where you can overwrite
the default configuration.
Flow
To use such behavior you need to import your own instance of Phrame2
into your codebase.
Load Phrame2 Env -> Config Overrides -> Output
Here's a basic structure with the ability to override config values:
├── components
│ └── MyAwsomeCmp
│ └── MyAwsomeCmp.js
│ └── MyAwsomeCmp.scss
│
└── scss
├── core.scss
└── config.scss
With these contents (and flow backwords to the root):
MyAwsomeCmp.js
import styles from './MyAwesomeCmp.scss'
MyAwsomeCmp.scss
// Always import this `core` instead of Phrame2 directly
@import '../../scss/core';
body {
background-color: _scheme('body.bg');
font-size: _config('size.large');
@include _on('mobile') {
background-color: _scheme('body.bg-on-mobile');
font-size: _config('size.sub-sizes.small');
}
}
core.scss
@import '~phrame2/core';
@import './config';
config.scss
// $_ is just a temporary assignment
// because this is how functions work in Sass
$_: _config('size', (
'small': 3.0em,
'big': 8.0em,
'large': 1.4em,
'sub-sizes': (
'small': 1.0em,
'big': 2.0em,
'large': 3.4em
)
));
Config Management
In case you're building a multi-(site/color) solution, or just need to make the environment configurable, you may end up having multiple configuration files.
Phrame2 comes with multiple solutions to manage these options/values easily.
@function _config(key[, value])
-
key
The key of the config property (size.small) -
value
(optional): any
Good to know: in fact, key is optional too. Returns full config object. Good for debugging purposes.
Good to know: this is just a shorthand function,
_config-set
and_config-get
is available too.
Usage examples
Override config
// Defaults
$_: _config('_namespace', (
'option-1': 'original-value-1',
'option-2': 'original-value-2',
'option-3': 'original-value-3'
));
// Overwrites
$_: _config('_namespace', (
'option-1': 'new-value-1',
'option-new-1': 'new-value-2'
));
// Results
$_: _config('_namespace', (
'option-1': 'new-value-1',
'option-2': 'original-value-2',
'option-3': 'original-value-3',
'option-new-1': 'new-value-2'
));
Merge multiple levels
// Defaults
$_: _config('_namespace', (
'option-1': (
'inner-option-1': 12px
),
'option-2': 'original-value-2'
));
// Overwrites
$_: _config('_namespace', (
'option-1': (
'new-inner-option': 'value'
),
'option-2': 'original-value-2'
));
// Results
$_: _config('_namespace', (
'option-1': (
'inner-option-1': 12px,
'new-inner-option': 'value'
),
'option-2': 'original-value-2'
));
Simplified write
// Defaults
$_: _config('_namespace', (
'option-1': (
'inner-option': 12px
),
'option-2': 'original-value-2'
));
// Set new value
$_: _config('_namespace.option-1.inner-option', 20px);
// Results
$_: _config('_namespace', (
'option-1': (
'inner-option': 20px
),
'option-2': 'original-value-2'
));
Built-in Tools
There are some built-in utilities based on the config management system.
All of them have shorthands, but under the hood the're using _config()
with a namespace like _scheme
.
These tools are made to manage the most common aspects:
- path: asset urls
- scheme: colors
- screen: responsiveness
- typography: sizes, families
@function _path($key[, $filename])
-
$key
The key of the path -
$filename
(optional) Name of the file
// Defaults from Phrame2
$_: _config('_path', (
'backgrounds': '../backgrounds/',
));
// Extend with your own
$_: _path('_path', (
'medals': (
'direct-bg': '../images/medals/bg.png',
'bronze': '../images/medals/bronze/',
'silver': '../images/medals/silver/',
'gold': '../images/medals/gold/'
)
));
// Use it
body {
background-image: url(_path('backgrounds', 'blue.jpg'));
}
.medals{
background-image: url(_path('medals.direct-bg'));
}
.medal {
&-gold {
background-image: url(_path('medals.gold', 'small.jpg'));
}
...
}
@function _scheme($key[, $value])
-
$key
Scheme key path -
$value
(optional) Color library reference or unit value
This is a tricky stuff, because built-in color management splits into 2 part:
- Color library
- Scheme
The Color library is responsibe to store the colors:
$_color-library: (
'transparent': transparent,
'white': (
'main': #ffffff
),
'black': (
'main': #000000
),
'red': (
'main': #ff0000
),
'yellow': (
'main': #ffcc00
),
'green': (
'main': #00ff00
),
'social': (
'facebook': #3B5998,
'twitter': #55ACEE,
'instagram': #3F729B,
'google-plus': #DD4B39,
'tumblr': #36465D
),
'gradient': (
'white-to-black': (#ffffff #000000)
)
);
Then use Scheme to define the colors for your elements.
$_scheme-config: (
// Commons
common: (
alert: 'yellow.main',
success: 'green.main',
error: 'red.main',
disabled: 'gray.main'
),
// Typography
text: (
base: 'black.main',
link: 'yellow.main',
link-hover: 'black.main'
)
);
The use it in your stylesheet:
.alert {
background-color: _scheme('common.alert');
}
What's the problem with using 1 level, or just variables?
Sure you can do the following in case you don't need such complexity.
$_: _config('colors', (
'main': #000,
'secondary': #fff
));
body {
color: _config('colors.main');
}
At a certian point you'll end up having the same color values repeated dozens of times. Keeping the available color palette in the Color Library solves this issue, helps you prevent duplicates and also provides a proper name (key) to get the correct color.
@function _color-library($key[, $value])
-
$key
Key of the value -
$value
(optional) Value itself
Shorthand function to get/set color-library related values.
@function _screen($key[, $value])
-
$key
Key of the value -
$value
(optional) Value itself
Shorthand function to get/set screen related values.
These values will be used by the built-in _on
mixin.
// Defaults
$_screen-config: (
size: (
mobile: (
from: 0,
to: 700
),
tablet: (
from: 701,
to: 1024,
),
small: (
from: 1025,
to: 1440
),
medium: (
from: 1441,
to: 1920,
),
large: (
from: 1921,
to: 3000
)
)
);
// Add a new screen size
$_: _screen('size.myDevice', (
from: 0,
to: 3000
));
// Or modify
$_: _screen('size', (
mobile: (
from: 0,
to: 625
)
));
@function _typo($key[, $value])
-
$key
Key of the value -
$value
(optional) Value itself
Shorthand function to get/set typography related values.
These values are used by the basic
module and stores
your typography related values.
// Defaults
$_typo-config: (
font-size: (
root: 62.5%, // Should not be changed, used for em calculations.
base: 14
),
line-height: (
root: 1.5
),
font-family: (
sans: unquote('Arial, Helvetica, sans-serif'),
serif: unquote('"Times New Roman", Times, serif'),
secondary: unquote('"Courier New", Courier, monospace')
)
);
pre, code {
font-family: _typo('font-family.secondary');
}
These values will be used by the basic
module, but it's optional.
See basic
module for more details.
@function _em($target, $base)
Function used for em
value calculations.
div {
font-size: 1.4em; // 14px
span {
font-size: 1em; // 14px
}
strong {
font-size: _em(20, 14); // 1.42857em === 20px
}
}
Please not that for this to work correctly, you need to use 62.5%
as your root font-size. See basic
module details about this.
@function _easing($key[, $value])
-
$key
Key of the easing -
$value
(optional) Easing value
Shorthand function to get/set easing related values.
// Usage
.has-transition {
transition: width 1s _easing('ease-in-out-expo');
}
.has-animation {
animation-timing-function: _easing('ease-in-out-expo');
}
Referencing keys
Sometimes you might want to say like:
"I want this text to have the same color always as the title."
Well you can do that using an @
sign.
When using references, you have to use full path.
$_: _config('subpage-scheme', (
'title': 'red.dark',
'this-text': '@subpage-scheme.title',
'other-text': '@whoknows.foo.bar'
));
This also helps to prevent to end up with such situations:
.not-app-title {
color: _scheme('app.background'); // Defined with the same color I needed
}
Mixins
@mixin _triangle($size, $color, $direction)
Creates a CSS triangle. Based on Zurb Foundation's solution.
-
$size
Size of triangle (px, em, etc.) -
$color
Hmm... Color of the triangle I guess. Use a color directly, or a scheme key. -
$direction
top
,right
,bottom
orleft
.tooltip {
&::before {
@include _triangle(
12px,
'tooltip.bg', // or simply #000
bottom
);
}
}
@mixin _scrollbar([$overrides])
Styles scrollbars where it's possible through CSS. Supports IE10+ and Chrome. Rest of the browsers doesn't allow, but hey, it's still a better solution than the shitty JS scrollbars!
-
$overrides
(optional) Override default values (coming from Scheme)
// Usage
body {
@include _scrollbar;
}
// For just one div
.div {
@include _scrollbar;
}
// Override default config
$_: _scheme('scrollbar', (
'thumb-background': 'grey.main'
));
// Direct value overwrite
.div {
@include _scrollbar((
'thumb-background': 'grey.main'
));
}
@mixin _on($breakpoint[, $up-down, $orientation])
Handles responsive rules
-
$breakpoint
Breakpoint name defined in_screen.scss
, orfrom
value, orretina
. -
$up-down
(optional) The given range and up/down, orto
value. -
$orientation
(optional)portrait
,landscape
// Usages
.title {
font-size: 1.3em;
// 0-700px
@include _on(mobile) {
font-size: 1em;
}
// 1024px and below
@include _on(tablet, down) {
font-size: 1em;
}
// 701px and up
@include _on(tablet, up) {
font-size: 1em;
}
// 1025px-1440px and portrait
@include _on(small, false, portrait) {
font-size: 1em;
}
// Only landscape
@include _on(false, false, landscape) {
font-size: 1em;
}
// Only on hdpi devices
@include _on(retina) {
font-size: 1em;
}
}
Modules
Modules are optional. Modules have output! It's a good practice to load these modules at boot level.
// Common usage and flow
@import '~phrame2/core';
@import 'myConfigOverrides';
@import '~phrame2/module/reset';
@import '~phrame2/module/basic';
@import '~phrame2/module/expose';
@import 'myOwnStuff';
basic
Includes some basic styles for your page. Here is the full code of the module, I'll walk through it now.
html {
// Root font size. 62.5% === 1em === 10px
font-size: _typo('font-size.root');
// We want the same size everywhere
-webkit-text-size-adjust: 100%;
}
// Base font
body {
font-family: _typo('font-family.sans');
font-weight: normal;
font-style: normal;
line-height: _typo('line-height.root');
}
// Bbox sizing as it should be
*, *:after, *:before {
box-sizing: border-box;
}
reset
A simple reset. You might not need this in case you already have your site built, or using some sort of framework, usually all framework are shipped with reset styles.
expose
This feature was usefull many times for us. It exposes your screen configuration (breakpoints)
as JSON in your CSS on body::before
. This will let you to have
the same rules in JavaScript also, the rules are have to be maintained
only from one place.
// Normalizes JSON
let normalize = s => s.replace(/^['"]+|\s+|\\|(;\s?})+|['"]$/g, '')
let breakpoints = JSON.parse(
normalize(
getComputedStyle(document.body, ':before').getPropertyValue('content')
)
)
Q&A
2
, where is the first one?
Why PhramePhrame1
is still alive. It's almost the same, except the bloat. It's like
those powerful frameworks out there seeing every day, and even more.
Using the same configuration system, it is generating modules based on configurations
like grid, form elements and such.
Then started to use CSS modules... I wanted to have a system I can import into the
Scss explicitly without any footprint. So basically Phrame2
is the shell of the first one.
Oh yeah, I've almost forgot. The name is also taken on npm :)
Why no more modules (grid, form elements, etc.) and other shiny mixins?
Because Phrame2
is more like a shell than a framework - unlike the first version.
Also there is no grid module because when I started to use CSS modules
with flexbox
,
I've realized it's easier to just write my needs on my own.
float
and inline-block
based grids are simple, it's easy to write a few classes, a generator around it.
This is mostly because they all follow this rule: ( 12 || 16 || 24 ) + ( px || em || % )
But flexbox
is flexible (oO). Every flexbox
based grids out there are totally different.
Simply because it is not designed for grids. CSS grids are for grids, but until
we have that in every modern browser, I'm fully satisfied with the bloat-free stylesheet and
bloat-free markup I get. It looks ugly in React components as well anyway.
However, since modules are optional, maybe I'll consider to have more modules included
(and/or port some from 1
), everyone can decide which one to use for his project.
Final words
As you can see, Phrame2
is not a full featured framework.
It's a higher order shell instead to kick start your own framework,
it helps to build your own system for your own custom needs.