CSS
This article covers multiple aspects related to styling of Cx in your application:
- Sass/SCSS
- Class prefixes and BESM convention
- Variables and Maps
- Importing SCSS files
- Themes
- Components
Sass
Styling of Cx widgets is implemented using Sass/SCSS. Sass stands for Syntactically Awesome Style Sheets and it's a professional CSS extension language. SCSS stands for Sassy CSS and it's a flavor of Sass which uses CSS-like (bracket based) syntax instead of indentation.
SCSS files are compiled into CSS using webpack's sass-loader which under the hood uses node-sass
library.
Before going further, users not familiar with Sass should learn the basic concepts, such as variables and mixins.
Class prefixes and BESM convention
All generated CSS classes use the default prefix cx. Furthermore, CSS classes generated
for each component follow the Block Element State Mod (BESM) convention.
BESM convention is inspired by the BEM convention, which provides guidelines for naming CSS classes and performance. BESM convention is slightly more relaxed and more suitable for components which appear in many different states.
- Block element classes start with the prefix
cxb-, e.g..cxb-window. - Element classes start with the prefix
cxe-and include the parent block name, e.g..cxe-window-header. - State modifiers start with the prefix
cxs-, e.g..cxs-disabled. - Style modifiers (mods) start with the prefix
cxm-, e.g..cxm-glow.
/// HTML generated by the ColorField widget
<div class="cxb-colorfield cxs-edit-mode cxs-visited">
<input
class="cxe-colorfield-input"
type="text"
id="fld-1859"
value="#f88"
>
<div class="cxe-colorfield-tool">
<div style="background-color: rgba(255, 136, 136, 1);"></div>
</div>
</div>
/// HTML generated by the Button widget with mod="primary"
<button class="cxb-button cxm-primary" type="button">
Primary
</button>By the convention, each component is represented by a single block-level element - .cxb-.
Each block may contain sub-elements which do not have any standalone meaning.
Element classes always include the name of the block and (in Cx) use a different prefix - .cxe-.
Components and elements may be in different states, which is represented by appending
additional CSS classes - state classes - .cxs. Finally, .cxm- classes are appended to the block
in order to change the component's appearance, which is also known as modding.
By using the BESM convention, Cx widgets are made very readable and easy to debug. Nevertheless, it's not mandatory to use the same convention in your application.
Variables and Maps
Cx offers many variables which can be used to tweak the default appearance of Cx widgets.
$cx-default-font-size: 16px;
$cx-default-input-border-color: #c2dad9;Some widgets have many different states and each state has its own visual properties. Cx uses Sass maps to represent style rules corresponding to each state.
$cx-list-item: (
default: (),
hover: (
background-color: rgba(128, 128, 128, 0.1),
outline: none
),
hover-focus: (
background-color: rgba(123,190,255, 0.15),
outline: none,
),
selected: (
background-color: rgba(128, 128, 128, 0.2),
),
selected-focus: (
background-color: rgba(123,190,255, 0.4),
outline: none
),
active: (
),
disabled: (
color: rgba(128, 128, 128, .5),
background-color: rgba(128, 128, 128, .1),
cursor: default
)
) !default;CSS overrides should be used to set the style rules that are not covered with the existing variables and maps.
Global rules
Sass maps are used for defining global rules. If you need to
add/override just a couple of rules, use cx-deep-map-merge instead
of replacing the entire map.
$cx-element-style-map: cx-deep-map-merge($cx-element-style-map, (
html: (
background: #222
),
a: (
text-decoration: none
)
));Importing SCSS files
Here, we'll assume that your application is also using SCSS. The process of importing Cx SCSS files needs to be performed in a below specified order, if you need to tweak the appearance of Cx widgets in your application.
// Here you have a chance to override Cx variables
@import "~cx/src/variables";
// You can override state-style-maps here, before importing CSS
//$cx-include-global-rules: false; // Include global rules (reset)
//$cx-include-all: true; // Include CSS for all components
@import "~cx/src/index";
// If $cx-include-all is set to false
//@include cx-textfield(); //include only the components you needThemes
Cx offers a couple of themes out of the box. In order to use a theme, install its npm package, e.g.
npm install cx-theme-dark
Then, import Cx styles from the package:
...
@import "~cx-theme-dark/src/variables";
...
@import "~cx-theme-dark/src/index";
...Beside CSS, a theme may include JavaScript changes as well. Because of this, the theme's
index.js file needs to be imported at the JavaScript's entry point of your Cx application.
import "cx-theme-frost/src";For more details, check the README.md file that comes with every theme package.
Components
Component-related SCSS code is placed next to the component JS implementation.
For example, the Button component is implemented in src/widgets/Button.js and the
same folder contains Button.scss and Button.variables.scss, which are related to styling.
Components that offer unique styling have a separate file for defining variables (e.g. Button.variables.scss).
Simpler components may not need a separate file for variables, and will only include a single
SCSS file which outputs CSS classes required by the component.
Subclassing Widgets
Subclassing means creating new widgets based on existing ones. For example, let's create a new search field widget based on the text field widget. It should have round borders, no outer borders and a semi-transparent placeholder color.
$searchfield-state-style-map: cx-merge-state-style-maps(
$cx-input-state-style-map, (
default: (
border-radius: 10px,
border-width: 0
)
)
);
@include cx-textfield('searchfield', $searchfield-state-style-map,
$placeholder: (
color: rgba(255, 255, 255, 0.5)
)
);You can use the new widget by specifying the base class:
<TextField baseClass="searchfield" ... />Better yet, you can subclass a new SearchField from the TextField widget in JavaScript as well.
class SearchField extends TextField {}
SearchField.prototype.baseClass = 'searchfield';After that, you can use it like this:
<SearchField ... />