Styles
You can define styles for a component. There are three ways to do this:
- You can style your component using BEM conventions and adding the component's styles to your project's stylesheet.
- You can provide your component with a style tag with styles for it inside the component's markup.
- You can use the NPM module stylor to create a virtual stylesheet scoped to your component.
BEM
BEM stands for Base-Element-Modifier. It's a system for naming the elements of a component in such a manner that the classes uniquely identify every part that needs to be styles. This results in CSS that is easier to reason about and maintain.
class List extends extends Component {
render(fruits) {
return (
<ul class='list--fruits'>
{
fruits.map(fruit =>
<li class='list--fruits__item'>
<p class='list--fruits__item__name'>{fruit.name}</p>
<p class='list--fruits__item__price'>${fruit.price}</p>
</li>)
}
<ul>
)
}
}
Notice how the class names clear define what each item is and what its relationship is to its parent. With these class we could create a stylesheet like this:
.list--fruits {
list-style: none;
margin: 1rem;
border: solid 1px #ccc;
}
.list--fruits__item {
margin: 0;
padding: 5px 10px;
border-bottom: solid 1px #ccc;
transition: all .25s ease-out;
}
.list--fruits__item:last-of-type {
border: none;
}
.list--fruits__item:hover {
cursor: pointer;
background-color: #333;
color: #fff;
}
.list--fruits__item__name {
color: blue;
}
.list--fruits__item__price {
color: green;
}
Using BEM to create class results in CSS that is easy to understand. Which list is this? Oh, the one for fruits, etc. Generally you shouldn't need to create nested selectors since the class names should clearly identify each element that gets that style. This eliminates the problem of cascading styles of one element affecting another element elsewhere on the page.
Style Tag in a Component
If you are creating an instance of the Component class, you want to define your styles in the render function and then pass them to the style tag inside your component's markup. In the example below, notice how we use the nav
tag's id to scope the styles to it:
import { h, Component } from 'composi'
export class Title extends Component {
container = 'header'
render (message) => {
// Define styles for style tag:
const styles = `
#nav {
padding: 10px;
background-color: #333;
}
#nav h1 {
color: #fff;
font-size: 2rem;
}`
// Define style tag in component:
return (
<nav id='nav'>
<style>
{styles}
</style>
<h1>Hello, {message}!</h1>
</nav>
)
}
}
If you are extending the Component class, the process is the same. Define your styles as a variable inside the `render` function before returning the markup:
import { h, Component, uuid } from 'composi'
class List extends Component {
constructor(props) {
super(props)
this.container = 'section'
}
render(data) {
const styles = `
.list {
list-style: none;
padding: 0;
margin: 10px;
border: solid 1px #ccc;
width: 300px;
}
.list > li {
margin: 0;
padding: 10px;
border-bottom: solid 1px #ccc;
}
.list > li:hover {
cursor: pointer;
}
.list > li:last-of-type {
border: none;
}
`
return (
<div id='list-container'>
<style>
{styles}
</style>
<ul class='list'>
{
data.map(item =>
<li key={item.id} data-id={item.id}>
<<input type="checkbox" checked={item.checked}/>
{item.name}
</li>)
}
</ul>
</div>
)
}
}
When you are using this technique, it is up to you to make sure the styles in the tag are scoped to the component. In the above examples we used an id on the base element of the component. However, if you want to have multiple instances of a component, then you might want to think about using BEM and add the styles directly to your project's stylesheet.
When Not to Use
Using this technique is a quick and simple way to add styles to a component. However, if the component's styles are complex, the presence of a large style declaration in the component can make it hard to read. In those situations you can either put the style declaration in a separate file and import it into the file where the component is being used, or simply put the styles in your project's stylesheet. You don't want a component's styles to be bigger than the component's code.
Inline Styles
You can provide your component elements with inline styles as well. You do this just like you always would, with a property followed by a string of valid CSS:
class List extends Component {
render(data) {
return (
<ul style="list-style: none; margin: 20px; border: solid 1px #ccc; border-bottom: none;">
{
data.map(item => <li style={{
borderBottom: 'solid 1px #ccc',
padding: '5px 10px'
}}>{item}</li>)
}
</ul>
)
}
})
Defining inline styles with JavaScript
You can also choose to define the inline style using JavaScript object notation. You would do this when you want to be able to use dynamic values created by your JavaScript. When defining your styles with JavaScript, the CSS properties that are hyphenated must be camel cased and all values other than pure numbers must be enclosed in quotes. Since the style property's value needs to be interpolated, the style definition needs to be enclosed in curly braces. Since this is an object, you need to make sure that there are double curly braces. The following is the same component as above, but with the styles define using JavaScript notation:
class List extends Component {
render(data) {
return (
<ul style={{
listStyle: 'none',
margin: '20px',
border: 'solid 1px #ccc',
borderBottom: 'none'
}}>
{
data.map(item => <li style={{
borderBottom: 'solid 1px #ccc',
padding: '5px 10px'
}}>{item}</li>)
}
</ul>
)
}
}
Since the style value is a JavaScript object, you can remove a style from within the markup and store it as a separate value. This is especially easy when you define a component in its own file:
// file: ./components/list.js
// Define inline styles as separate objects:
const listStyle = {
listStyle: 'none',
margin: '20px',
border: 'solid 1px #ccc',
borderBottom: 'none'
}
const listItemStyle = {
borderBottom: 'solid 1px #ccc',
padding: '5px 10px'
}
// Pass style objects to component:
class List extends Component({
render(data) {
return (
<ul style={listStyle}>
{
data.map(item => <li style={listItemStyle}>{item}</li>)
}
</ul>
)
}
}
Although inline styles result in highly portable styled components, unless you separate out the style definitions into separate objects, they result in markup that is harder to read. Also inline styles have a major defect that they only allow styling the element they are on. You cannot style pseudo-elements or use media queries, etc. If you want a component to have encapsulated style without these limitation, consider using the stylor
module explained next.
Using Stylor
You can use the NPM module stylor to create a virtual stylesheet scoped to your components. You will do this inside the component's componentDidMount
lifecyle method. This requires the styles to be defined as a JavaScript object. Properties must be camel cased and values must be quoted. If you want, you can use hypenated properties by enclosing them in quotes. Simple selectors are fine, but complex properties will need to be enclosed in quotes. You can use hierachical nesting to define parent child relationships, similar to how LESS and SASS do. If the value for a property will be a pixel value, you do not need to provide the px
. values of plain numbers will get converted to pixel values.
Installing Stylor
Use NPM:
# cd to the project folder and run this:
npm i -D stylor
Importing Stylor into Your Project
After installing stylor
as a dependency of your project, you need to import it in your project. In whichever file you want to use it, import it like this:
import { createStylesheet } from 'stylor'
After importing createStylesheet
from stylor
you can use it to create a stylesheet for a component. The createStylesheet
function takes an object with two properties: base
and styles
. base
is a selector for the element from which styles will be scoped. This should be a unique selector, preferrably with an id. styles
is an object defining the styles to be created.
Here is an example of styles set up for a Component instance:
class PersonComponent extends Component {
render(person) {
return (
<div>
<p>Name: {person.name.last}, {person.name.first}</p>
<p>Job: {person.job}</p>
<p>Employer: {person.employer}</p>
<p>Age: {person.age}</p>
</div>
)
}
componentDidMount() {
// Define conponent-scoped styles:
createStylesheet({
base: '#person',
styles: {
border: 'solid 1px #ccc',
margin: '1em',
p: {
margin: 0,
padding: '5px 10px',
// Hover effect for p tags:
':hover': {
backgroundColor: '#ddd',
color: '#fff',
cursor: 'pointer'
}
}
}
})
}
})
An here is an example of styles set up for when extending Component:
class Person extends Component {
constructor(opts) {
super(opts)
this.container = '#person'
this.state = personData
}
render = (person) => (
<div>
<p>Name: {person.name.last}, {person.name.first}</p>
<p>Job: {person.job}</p>
<p>Employer: {person.employer}</p>
<p>Age: {person.age}</p>
</div>
)
componentDidMount() {
// Define conponent-scoped styles:
createStylesheet({
base: '#person',
styles: {
border: 'solid 1px #ccc',
margin: '1em',
p: {
margin: 0,
padding: '5px 10px',
// Hover effect for p tags:
':hover': {
backgroundColor: '#ddd',
color: '#fff',
cursor: 'pointer'
}
}
}
})
}
}
And here's a sample of some general styles
objects from an actual component. You can see that we can target element inside a component. Because the styles get scoped to the component, these styles will not leak out and affect other elements in the page.
styles: {
label: {
display: 'inline-block',
width: 100,
textAlign: 'right'
},
button: {
color: '#fff',
backgroundColor: 'green'
},
'button.delete': {
transition: 'all .25s ease-out',
border: 'solid 1px red',
backgroundColor: 'red',
color: '#fff',
padding: '10px 20px',
':hover': {
backgroundColor: '#fff',
color: 'red',
}
},
'li:last-of-type': {
borderBottom: 'none'
},
input: {
width: 150
}
}
Here's another sample styles
object:
styles: {
div: {
margin: 20,
span: {
position: 'relative',
top: '-1px',
display: 'inline-block',
border: 'solid 1px #007aff',
padding:' 5px 10px 5px',
minWidth: '20px',
textAlign: 'center'
},
button: {
fontSize: '13pt',
border: 'solid 1px #007aff',
color: '#fff',
backgroundColor: '#007aff',
padding: '3px 10px 3px',
cursor: 'pointer',
margin: 0,
':first-of-type': {
borderRight: 'none'
},
':nth-child(3)': {
borderLeft: 'none'
},
':last-of-type': {
backgroundColor: 'red',
borderColor: 'red',
color: '#fff',
marginLeft: 10,
':hover': {
backgroundColor: '#fff',
color: 'red'
}
},
':hover': {
backgroundColor: '#fff',
color: '#007aff',
borderColor: '#007aff'
},
'[disabled]': {
backgroundColor: '#ccc',
borderColor: '#ccc',
cursor: 'default',
opacity: '.75',
':hover': {
backgroundColor: '#ccc',
color: '#007aff',
borderColor: '#ccc'
}
}
}
}
}
Scoped Stylesheets and Memory
When you define styles on a class constructor, each instance of the class will have its own virtual stylesheet created. This is fine if the number of instances are not large. You should, however, bare in mind that each scoped stylesheet takes up memory. If you intend to create many instances of the same component, it might make sense to not create a scope style but to instead put the styles that all instances will share in your project's stylesheet.
SASS, LESS, POST-CSS
If you want, you can use SASS, LESS or PostCSS as CSS preprocessors in your project. To do so you will have to use the `gulp` versions. For SASS, use gulp-sass, or LESS use gulp-less and for PostCSS use postcss. Just install these like this:
npm i -D gulp-sass
// or
npm i -D gulp-less
// or
npm i -D gulp-postcss
Then add these to your project's gulpfile:
/////////////
// For SASS:
/////////////
const sass = require('gulp.sass');
// Define a task for SASS:
gulp.task('sass', function () {
gulp.src('./scss/.scss')
.pipe(sass())
.pipe(gulp.dest('./css'))
});
// Then add new SASS task to build:
gulp.task('default', ['sass', 'serve', 'watch'])
////////////
// For LESS:
////////////
const less = require('gulp-less')
// Define a task for LESS:
gulp.task('less', function () {
return gulp.src('./less/**/*.less')
.pipe(less({
paths: [ path.join(__dirname, 'less', 'includes') ]
}))
.pipe(gulp.dest('./css'))
})
// Then add new LESS task to build:
gulp.task('default', ['less', 'serve', 'watch'])
//////////////
// For PostCSS
//////////////
const postcss = require('gulp-postcss');
// Define a task for PostCSS:
gulp.task('postcss', function () {
return gulp.src('./src/*.css')
.pipe(postcss())
.pipe(gulp.dest('./css'))
})
// Then add new PostCSS task to build:
gulp.task('default', ['postcss', 'serve', 'watch'])
Font Size
A Composi project includes the Boostrap 4.0 CSS reset. This has one slight change affecting font sizes. The html
tag has its font size set to 65.5%. And the body
tag has its font size set to 1.4rem. In fact, all font sizes in this stylesheet are set with rem
. This gives you a more flexible and responsive result.
Using Rem
When using rem
, if you want an equivalent to pixel values, add a decimal to it:
1.4rem = 14px
1.6rem = 16px
.5rem = 5px
1rem = 10px
2rem = 20px
Since a rem
value is set on the body tag, all other rem
values will be based on that. You can change the proportion of fonts in your app by changing the rem
value of the body tag. You can do this by opening your project's styles.css
file. Scroll to the bottom where you'll find the comment /* Local Styles */
. Any where after that you can add your own desired value for your project's base font size:
body {
/* font base of 16px */
font-size: 1.6rem
/* or font base of 12px */
font-size: 1.2rem
}