Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

aesthetic

Package Overview
Dependencies
Maintainers
1
Versions
81
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

aesthetic

Aesthetic is a powerful React library for styling components through the use of adapters.

  • 2.1.0
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
98
decreased by-3.92%
Maintainers
1
Weekly downloads
 
Created
Source

Aesthetic

Build Status

Aesthetic is a powerful React library for styling components, whether it be CSS-in-JS using style objects, importing stylesheets, or simply referencing external class names. Simply put, Aesthetic is an abstraction layer that utilizes higher-order-components for the compilation of styles via third-party libraries, all the while providing customizability, theming, and a unified syntax.

import React from 'react';
import PropTypes from 'prop-types';
import { StylesPropType } from 'aesthetic';
import withStyles, { classes } from '../path/to/styler';

class Carousel extends React.Component {
  static propTypes = {
    children: PropTypes.node,
    styles: StylesPropType.isRequired,
  };

  // ...

  render() {
    const { children, styles } = this.props;
    const { animating } = this.state;

    return (
      <div
        role="tablist"
        className={classes(
          styles.carousel,
          animating && styles.carousel__animating,
        )}
      >
        <ul className={classes(styles.list)}>
          {children}
        </ul>

        <button
          type="button"
          onClick={this.handlePrev}
          className={classes(styles.button, styles.prev)}
        ></button>

        <button
          type="button"
          onClick={this.handleNext}
          className={classes(styles.button, styles.next)}
        ></button>
      </div>
    );
  }
}

export default withStyles({
  carousel: {
    position: 'relative',
    maxWidth: '100%',
    // ...
  },
  carousel__animating: { ... },
  list: { ... },
  button: { ... },
  prev: { ... },
  next: { ... },
})(Carousel);

Aesthetic was built for the sole purpose of solving the following scenarios, most of which competing styling libraries fail to solve.

Multiple styling patterns

Want to use external CSS or Sass files? Or maybe CSS modules? Or perhaps CSS-in-JS? What about JSS instead of Aphrodite? All of these patterns and choices are supported through the use of adapters. However, inline styles are not supported as we prefer the more performant option of compiling styles and attaching them to the DOM.

Styling third-party libraries

Using a third-party provided UI component library has the unintended side-effect of hard-coded and non-customizable styles. Aesthetic solves this by allowing consumers to extend and inherit styles from the provided base component.

Requirements

  • React 15/16+
  • IE 11+
  • WeakMap

Installation

Aesthetic requires React as a peer dependency.

npm install aesthetic react --save
// Or
yarn add aesthetic react

Documentation

Initial Setup

Aesthetic makes heavy use of process.env.NODE_ENV for logging errors in development. These errors will be entirely removed in production if the following build steps are configured.

Webpack

DefinePlugin plugin is required when using Webpack.

new webpack.DefinePlugin({
  'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production'),
}),
Browserify

Envify transformer is required when using Browserify.

envify({
  NODE_ENV: process.env.NODE_ENV || 'production',
});

Style Adapters

An adapter in the context of Aesthetic is a third-party library that supports CSS in JavaScript, whether it be injecting CSS styles based off JavaScript objects, importing CSS during a build process, or simply referencing CSS class names.

The following libraries and their features are officially supported by Aesthetic.

AdapterUnified SyntaxGlobals¹PseudosFallbacksFontsAnimationsMedia QueriesSupportsSpecificity
CSS class names
CSS modules
Aphrodite✓²
Fela
Glamor
JSS
TypeStyle
  1. Unified syntax only.
  2. Is accomplished through a custom global selector handler.

The following libraries are currently not supported.

  • CSSX - Does not generate unique class names during compilation and instead uses the literal class names and or tag names defined in the style declaration. This allows for global style collisions, which we want to avoid.
  • Styletron - Currently does not support animations, font faces, or globals. Will revisit in the future.

Creating A Styler

To start using Aesthetic, a styler function must be created. This styler function acts as a factory for the creation of higher-order-components (HOC). These HOC's are used in passing down styles to the original wrapped component.

To begin, we must instantiate Aesthetic with an adapter, and pass it to createStyler to create the style and transform functions. The style function is the HOC factory, while transform will combine and process multiple style objects into a CSS class name.

import Aesthetic, { createStyler } from 'aesthetic';
import JSSAdapter from 'aesthetic-adapter-jss'; // Or your chosen adapter

const { style, transform } = createStyler(new Aesthetic(new JSSAdapter()));

export const classes = transform; // Or another utility name
export default style;

I suggest doing this an a file that can be imported for reusability.

Once we have a styler function, we can import it and wrap our React components. The styler function accepts a style sheet as its 1st argument, and an object of configurable options as the second. The following options are supported.

  • styleName (string) - The unique style name of the component. This name is primarily used in logging and caching. Defaults to the component or function name.
  • extendable (boolean) - Allows the component and its styles to be extended, creating a new component in the process. Defaults to false.
  • stylesPropName (string) - Name of the prop in which the style sheet is passed to. Defaults to styles.
  • themePropName (string) - Name of the prop in which the theme sheet is passed to. Defaults to theme.
  • pure (boolean) - When true, the higher-order-component will extend React.PureComponent instead of React.Component. Only use this for static/dumb components.
export default withStyles({
  button: { ... },
}, {
  styleName: 'CustomButton',
  extendable: true,
  pure: true,
  stylesPropName: 'styleSheets',
  themePropName: 'appTheme',
})(Button);

If you get tired of passing stylesPropName, themePropName, pure, and extendable to every component, you can pass these as default options to the Aesthetic instance.

new Aesthetic(adapter, {
  extendable: true,
  pure: true,
  stylesPropName: 'styleSheets',
  themePropName: 'appTheme',
})

Defining Components

Now that we have a styler function, we can start styling our components by wrapping the component declaration with the styler function and passing an object of styles. When this component is rendered, the style sheet is passed to the styles prop, and we can generate a class name using the transform function (classes in the example).

import React, { PropTypes } from 'react';
import { StylesPropType } from 'aesthetic';
import withStyles, { classes } from '../path/to/styler';

function Button({ children, styles, icon }) {
  return (
    <button type="button" className={classes(styles.button)}>
      {icon && (
        <span className={classes(styles.icon)}>{icon}</span>
      )}

      {children}
    </button>
  );
}

Button.propTypes = {
  children: PropTypes.node,
  styles: StylesPropType.isRequired,
  icon: PropTypes.node,
};

export default withStyles({
  button: { ... },
  icon: { ... }
})(Button);
Customizing Styles

Since styles are isolated and co-located within a component, they can be impossible to customize, especially if the component comes from a third-party library. If a component styled by Aesthetic is marked as extendable, styles can be customized by calling the static extendStyles method on the wrapped component instance.

Extending styles will return the original component wrapped with new styles, instead of wrapping the styled component and stacking on an unnecessary layer.

import BaseButton from '../path/to/styled/Button';

export const Button = BaseButton.extendStyles({
  button: {
    background: 'white',
    // ...
  },
});

export const PrimaryButton = BaseButton.extendStyles({
  button: {
    background: 'blue',
    // ...
  },
});

Parent styles (the component that was extended) are automatically merged with the new styles.

Using Classes

When applying or combining class names to a component, the transform function provided by createStyler must be used. This function accepts an arbitrary number of arguments, all of which can be strings or style objects that evaluate to truthy.

import withStyles, { classes } from '../path/to/styler';

classes(
  styles.foo,
  expression && styles.bar,
  expression ? styles.baz : styles.qux,
);

Using our Button examples above, let's add an active state and combine classes like so. Specificity is important, so define styles from top to bottom!

function Button({ children, styles, icon, active = false }) {
  return (
    <button
      type="button"
      className={classes(
        styles.button,
        active && styles.button__active,
      )}
    >
      {icon && (
        <span className={classes(styles.icon)}>{icon}</span>
      )}

      {children}
    </button>
  );
}

Styling Components

As mentioned previously, to style a component, an object or function must be passed as the 1st argument to the styler function. This object represents a mapping of selectors (and modifiers) to declarations. For example:

withStyles({
  button: { ... },
  button__active: { ... },
  icon: { ... },
})(Button)

The following types of declarations are permitted.

External Classes

External CSS class names can be referenced by passing a string of the class name.

withStyles({
  button: 'button',
  button__active: 'button--active',
  icon: 'button__icon',
})(Button)

To only make use of class names, the provided ClassNameAdapter must be used.

Style Objects

CSS styles can be defined using an object of properties to values. These objects are transformed using adapters and optionally support the unified syntax defined by Aesthetic.

withStyles({
  button: {
    background: '#eee',
    // ...
  },
  button__active: {
    background: '#fff',
    // ...
  },
  icon: {
    display: 'inline-block',
    verticalAlign: 'middle',
    // ...
  },
})(Button)
Style Functions

Style functions are simply functions that return a style object. The benefits of using a function is that it provides the current theme as the 1st argument, and the current React component props as the 2nd argument.

withStyles((theme, props) => {
  // ...
})(Button)

Theming Components

Themes are great in that they enable components to be styled in different ways based on pre-defined style guide parameters, like font size, color hex codes, and more.

To make use of a theme, register it through the Aesthetic instance using registerTheme. This method accepts a name, an object of parameters, and an optional style object used for globals (like font faces and animation keyframes).

aesthetic.registerTheme('dark', {
  unit: 'em',
  unitSize: 8,
  spacing: 5,
  font: 'Open Sans',
  bgColor: 'darkgray',
}, {
  '@global': {
    body: {
      margin: 0,
      padding: 0,
      height: '100%',  
    },
  },
  '@font-face': {
    'Open Sans': {
      fontStyle: 'normal',
      fontWeight: 'normal',
      srcPaths: ['fonts/OpenSans.woff'],
    },
  },
});

Global styles are immediately compiled and attached to the DOM. Be wary of conflicts.

If you'd like to extend a base theme to create a new theme, use extendTheme. This method accepts the theme name to inherit from as the first argument, with the remaining arguments matching registerTheme.

aesthetic.extendTheme('dark', 'darker', {
  bgColor: 'black',
});

Extending themes will deep merge the two parameter objects.

Using Theme Styles

Once a theme has been registered, we can access the theme parameters by using a style function. The parameters object is passed as the 1st argument to the function.

withStyles(theme => ({
  button: {
    fontSize: `${theme.unitSize}${theme.unit}`,
    fontFamily: theme.font,
    padding: theme.spacing,
  },
}))(Component);

The theme style declaration can be accessed within a component via the theme prop.

Activating Themes

To activate and inform components to use a specific theme, we must use the ThemeProvider, which accepts a name of the theme.

import { ThemeProvider } from 'aesthetic';

<ThemeProvider name="default">
  // All components within here will use the "default" theme

  <ThemeProvider name="dark">
    // And all components here will use the "dark" theme
  </ThemeProvider>
</ThemeProvider>

Or by passing a themeName prop to an individual component.

<Button themeName="dark">Save</Button>

Or by setting the default theme on the Aesthetic instance.

new Aesthetic(adapter, { defaultTheme: 'default' });

Unified Syntax

Aesthetic provides an optional unified CSS-in-JS syntax. This unified syntax permits easy drop-in replacements between adapters that utilize CSS-in-JS objects, as well as a standard across libraries.

Pros

  • Easily swap between CSS-in-JS adapters (for either performance or extensibility reasons) without having to rewrite all CSS style object syntax.
  • Third-party UI libraries can define their styles using the unified syntax, while consumers can choose their preferred adapter.
  • Third-party UI libraries can standardize on a single syntax for interoperability.
  • Only have to learn one form of syntax.

Cons

  • Slight overhead (like milliseconds) converting the unified syntax to the adapters native syntax. However, Aesthetic caches heavily.
  • Must learn a new form of syntax (hopefully the last one).

Why a new syntax?

While implementing adapters and writing tests for all their syntax and use cases, I noticed that all adapters shared about 90-95% of the same syntax. That remaining percentage could easily be abstracted away by a library, and hence, this unified syntax was created.

Furthermore, a unified syntax allows providers of third-party components to define their styles in a standard way with consumers having the choice of their preferred adapter.

Why a different at-rule structure?

The major difference between the unified syntax and native adapters syntax, is that at-rules in the unified syntax are now multi-dimensional objects indexed by the name of the at-rule (@media), while at-rules in the native syntax are single objects indexed by the at-rule declaration (@media (min-width: 100px)).

Supporting the native syntax incurred an linear (O(n)) lookup, as we would have to loop through each object recursively to find all at-rules, while the unified syntax is a simple constant (O(1)) lookup as we know the names ahead of time. This constant time lookup is what enables a fast conversion process between the unified and native syntaxes.

How do I enable the unified syntax?

Please refer to the readme of your chosen adapter.

Properties

Standard structure for defining properties.

  • Supports camel case property names.
  • Units can be written as literal numbers.
{
  button: {
    margin: 0,
    padding: 5,
    display: 'inline-block',
    lineHeight: 'normal',
    textAlign: 'center',
    cursor: 'pointer',
    backgroundColor: '#ccc',
    color: '#000',
  },
  buttonGroup: {
    // ...
  },
}

Fela requires the fela-plugin-unit plugin.

JSS requires the jss-default-unit, jss-camel-case, and jss-global plugins.

Pseudos

Pseudo elements and classes are defined inside a selector as nested objects.

{
  button: {
    // ...
    ':hover': {
      backgroundColor: '#eee',
    },
    '::before': {
      content: '"★"',
      display: 'inline-block',
      marginRight: 5,
    },
  },
}

JSS requires the jss-nested plugin.

Selectors

Parent, child, and sibling selectors are purposefully not supported. Use unique and isolated element selectors and style declarations instead.

Global At-rules

Not to be confused with global styles, global at-rules are at-rules that must be defined in the root of a style sheet and cannot be defined within a selector.

Not all adapters support every global at-rule.

@charset

Supported by JSS.

{
  '@charset': 'utf8',
}
@font-face

Supported by all adapters.

{
  '@font-face': {
    'Open Sans': {
      fontStyle: 'normal',
      fontWeight: 'normal',
      srcPaths: ['fonts/OpenSans.woff2', 'fonts/OpenSans.ttf'],
    },
  },
  button: {
    // ...
    fontFamily: 'Open Sans',
  },
}

The fontFamily property can be omitted as it'll be inherited from the property name.

To support multiple font variations, like bold and italics, pass an array of declarations.

{
  '@font-face': {
    'Open Sans': [
      {
        fontStyle: 'normal',
        fontWeight: 'normal',
        srcPaths: ['fonts/OpenSans.woff2', 'fonts/OpenSans.ttf'],
      },
      {
        fontStyle: 'italic',
        fontWeight: 'normal',
        srcPaths: ['fonts/OpenSans-Italic.woff2', 'fonts/OpenSans-Italic.ttf'],
      },
      {
        fontStyle: 'normal',
        fontWeight: 'bold',
        srcPaths: ['fonts/OpenSans-Bold.woff2', 'fonts/OpenSans-Bold.ttf'],
      },
    ],
  },
}

Lastly, to define local() source aliases, pass an array of strings to a local property.

{
  '@font-face': {
    'Open Sans': {
      fontStyle: 'normal',
      fontWeight: 'normal',
      local: ['OpenSans', 'Open-Sans'],
      srcPaths: ['fonts/OpenSans.ttf'],
    },
  },
}
@global

Supported by Aphrodite, Fela, JSS, and TypeStyle.

{
  '@global': {
    body: {
      margin: 0,
      padding: 0,
      fontSize: 16,
    },
    'body, html': {
      height: '100%',
    },
    a: {
      color: 'red',
      ':hover': {
        color: 'darkred',
      },
    },
  },
}

JSS requires the jss-global plugin.

@import

Supported by JSS.

{
  '@import': 'css/reset.css',
}
@keyframes

Supported by all adapters.

{
  '@keyframes': {
    fade: {
      from: { opacity: 0 },
      to: { opacity: 1 },
    },
  },
  button: {
    // ...
    animationName: 'fade',
    animationDuration: '3s',
  },
}
@namespace

Supported by JSS.

{
  '@namespace': 'url(http://www.w3.org/1999/xhtml)',
}
@page

Currently supported by no adapters.

{
  '@page': {
    margin: '1cm',
  },
}

:left, :right, and other pseudos are not supported.

@viewport

Supported by JSS.

{
  '@viewport': {
    width: 'device-width',
    orientation: 'landscape',
  },
}
Local At-rules

Local at-rules are at-rules that must be defined within a selector and cannot be defined in the root of a style sheet.

@fallbacks

Supported by Fela, Glamor, JSS, and TypeStyle.

{
  wrapper: {
    // ...
    background: 'linear-gradient(...)',
    display: 'flex',
    '@fallbacks': {
      background: 'red',
      display: ['box', 'flex-box'],
    },
  },
}

Aphrodite does not support fallback styles.

Fela requires the fela-plugin-fallback-value plugin.

@media

Supported by all adapters.

tooltip: {
  // ...
  maxWidth: 300,
  '@media': {
    '(min-width: 400px)': {
      maxWidth: 'auto',
    },
  },
},

Nested @media are currently not supported.

@supports

Supported by Fela, Glamor, JSS, and TypeStyle.

grid: {
  // ...
  float: 'left',
  '@supports': {
    '(display: flex)': {
      float: 'none',
      display: 'flex',
    },
  },
},

Nested @supports are currently not supported.

Competitors Comparison

A brief comparison of Aesthetic to competing React style abstraction libraries.

Features
aestheticreact-with-stylesstyled-componentsradium
AbstractionHOCHOCTemplate LiteralsHOC
TypeClassesClasses, Inline stylesClassesInline styles
Unified Syntax
CachingN/A
Themes
Style Extending
Adapters
aestheticreact-with-stylesstyled-componentsradium
CSS class names
CSS Modules
Aphrodite
Fela
Glamor
JSS
TypeStyle

Keywords

FAQs

Package last updated on 04 Jan 2018

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc