1 | # Aphrodite [![npm version](https://badge.fury.io/js/aphrodite.svg)](https://badge.fury.io/js/aphrodite) [![Build Status](https://travis-ci.org/Khan/aphrodite.svg?branch=master)](https://travis-ci.org/Khan/aphrodite) [![Coverage Status](https://coveralls.io/repos/github/Khan/aphrodite/badge.svg?branch=master)](https://coveralls.io/github/Khan/aphrodite?branch=master) [![Gitter chat](https://img.shields.io/gitter/room/Khan/aphrodite.svg)](https://gitter.im/Khan/aphrodite) [![gzip size][gzip-badge]][unpkg-dist] [![size][size-badge]][unpkg-dist]
|
2 |
|
3 | _Framework-agnostic CSS-in-JS with support for server-side rendering, browser prefixing, and minimum CSS generation._
|
4 |
|
5 | Support for colocating your styles with your JavaScript component.
|
6 |
|
7 | - Works great with and without React
|
8 | - Supports media queries without window.matchMedia
|
9 | - Supports pseudo-selectors like `:hover`, `:active`, etc. without needing to
|
10 | store hover or active state in components. `:visited` works just fine too.
|
11 | - Supports automatic global `@font-face` detection and insertion.
|
12 | - Respects precedence order when specifying multiple styles
|
13 | - Requires no AST transform
|
14 | - Injects only the exact styles needed for the render into the DOM.
|
15 | - Can be used for server rendering
|
16 | - Few dependencies, small (20k, 6k gzipped)
|
17 | - No external CSS file generated for inclusion
|
18 | - Autoprefixes styles
|
19 |
|
20 | # Installation
|
21 |
|
22 | Aphrodite is distributed via [npm](https://www.npmjs.com/):
|
23 |
|
24 | ```
|
25 | npm install --save aphrodite
|
26 | ```
|
27 |
|
28 | # API
|
29 |
|
30 | If you'd rather watch introductory videos, you can find them [here](https://www.youtube.com/playlist?list=PLo4Zh55ZzNSBP78pCD0dZJi9zf8CA72_M).
|
31 |
|
32 | ```jsx
|
33 | import React, { Component } from 'react';
|
34 | import { StyleSheet, css } from 'aphrodite';
|
35 |
|
36 | class App extends Component {
|
37 | render() {
|
38 | return <div>
|
39 | <span className={css(styles.red)}>
|
40 | This is red.
|
41 | </span>
|
42 | <span className={css(styles.hover)}>
|
43 | This turns red on hover.
|
44 | </span>
|
45 | <span className={css(styles.small)}>
|
46 | This turns red when the browser is less than 600px width.
|
47 | </span>
|
48 | <span className={css(styles.red, styles.blue)}>
|
49 | This is blue.
|
50 | </span>
|
51 | <span className={css(styles.blue, styles.small)}>
|
52 | This is blue and turns red when the browser is less than
|
53 | 600px width.
|
54 | </span>
|
55 | </div>;
|
56 | }
|
57 | }
|
58 |
|
59 | const styles = StyleSheet.create({
|
60 | red: {
|
61 | backgroundColor: 'red'
|
62 | },
|
63 |
|
64 | blue: {
|
65 | backgroundColor: 'blue'
|
66 | },
|
67 |
|
68 | hover: {
|
69 | ':hover': {
|
70 | backgroundColor: 'red'
|
71 | }
|
72 | },
|
73 |
|
74 | small: {
|
75 | '@media (max-width: 600px)': {
|
76 | backgroundColor: 'red',
|
77 | }
|
78 | }
|
79 | });
|
80 | ```
|
81 |
|
82 | ## Conditionally Applying Styles
|
83 |
|
84 | Note: If you want to conditionally use styles, that is simply accomplished via:
|
85 |
|
86 | ```jsx
|
87 | const className = css(
|
88 | shouldBeRed() ? styles.red : styles.blue,
|
89 | shouldBeResponsive() && styles.small,
|
90 | shouldBeHoverable() && styles.hover
|
91 | )
|
92 |
|
93 | <div className={className}>Hi</div>
|
94 | ```
|
95 |
|
96 | This is possible because any falsey arguments will be ignored.
|
97 |
|
98 | ## Combining Styles
|
99 |
|
100 | To combine styles, pass multiple styles or arrays of styles into `css()`. This is common when combining styles from an owner component:
|
101 |
|
102 | ```jsx
|
103 | class App extends Component {
|
104 | render() {
|
105 | return <Marker styles={[styles.large, styles.red]} />;
|
106 | }
|
107 | }
|
108 |
|
109 | class Marker extends Component {
|
110 | render() {
|
111 | // css() accepts styles, arrays of styles (including nested arrays),
|
112 | // and falsy values including undefined.
|
113 | return <div className={css(styles.marker, this.props.styles)} />;
|
114 | }
|
115 | }
|
116 |
|
117 | const styles = StyleSheet.create({
|
118 | red: {
|
119 | backgroundColor: 'red'
|
120 | },
|
121 |
|
122 | large: {
|
123 | height: 20,
|
124 | width: 20
|
125 | },
|
126 |
|
127 | marker: {
|
128 | backgroundColor: 'blue'
|
129 | }
|
130 | });
|
131 | ```
|
132 |
|
133 | ## Resetting Style Cache
|
134 |
|
135 | The `reset` function can be used to reset the HTML style tag, injection buffer, and injected cache. Useful when Aphrodite needs to be torn down and set back up.
|
136 |
|
137 | ```js
|
138 | import { reset } from 'aphrodite';
|
139 |
|
140 | reset();
|
141 | ```
|
142 |
|
143 | While the `resetInjectedStyle` function can be used to reset the injected cache for a single key (usually the class name).
|
144 |
|
145 | ```js
|
146 | import { resetInjectedStyle } from 'aphrodite';
|
147 |
|
148 | resetInjectedStyle('class_1sAs8jg');
|
149 | ```
|
150 |
|
151 | ## Server-side rendering
|
152 |
|
153 | To perform server-side rendering, make a call to `StyleSheetServer.renderStatic`, which takes a callback. Do your rendering inside of the callback and return the generated HTML. All of the calls to `css()` inside of the callback will be collected and the generated css as well as the generated HTML will be returned.
|
154 |
|
155 | Rehydrating lets Aphrodite know which styles have already been inserted into the page. If you don't rehydrate, Aphrodite might add duplicate styles to the page.
|
156 |
|
157 | To perform rehydration, call `StyleSheet.rehydrate` with the list of generated class names returned to you by `StyleSheetServer.renderStatic`.
|
158 |
|
159 | Note: If you are using `aphrodite/no-important` in your project and you want to render it on server side, be sure to import `StyleSheetServer` from `aphrodite/no-important` otherwise you are going to get an error.
|
160 |
|
161 | As an example:
|
162 |
|
163 | ```js
|
164 | import { StyleSheetServer } from 'aphrodite';
|
165 |
|
166 | // Contains the generated html, as well as the generated css and some
|
167 | // rehydration data.
|
168 | var {html, css} = StyleSheetServer.renderStatic(() => {
|
169 | return ReactDOMServer.renderToString(<App/>);
|
170 | });
|
171 |
|
172 | // Return the base HTML, which contains your rendered HTML as well as a
|
173 | // simple rehydration script.
|
174 | return `
|
175 | <html>
|
176 | <head>
|
177 | <style data-aphrodite>${css.content}</style>
|
178 | </head>
|
179 | <body>
|
180 | <div id='root'>${html}</div>
|
181 | <script src="./bundle.js"></script>
|
182 | <script>
|
183 | StyleSheet.rehydrate(${JSON.stringify(css.renderedClassNames)});
|
184 | ReactDOM.render(<App/>, document.getElementById('root'));
|
185 | </script>
|
186 | </body>
|
187 | </html>
|
188 | `;
|
189 | ```
|
190 |
|
191 | ## Disabling `!important`
|
192 |
|
193 | By default, Aphrodite will append `!important` to style definitions. This is
|
194 | intended to make integrating with a pre-existing codebase easier. If you'd like
|
195 | to avoid this behaviour, then instead of importing `aphrodite`, import
|
196 | `aphrodite/no-important`. Otherwise, usage is the same:
|
197 |
|
198 | ```js
|
199 | import { StyleSheet, css } from 'aphrodite/no-important';
|
200 | ```
|
201 |
|
202 | ## Minifying style names
|
203 |
|
204 | By default, Aphrodite will minify style names down to their hashes in production
|
205 | (`process.env.NODE_ENV === 'production'`). You can override this behavior by
|
206 | calling `minify` with `true` or `false` before calling `StyleSheet.create`.
|
207 |
|
208 | This is useful if you want to facilitate debugging in production for example.
|
209 |
|
210 | ```js
|
211 | import { StyleSheet, minify } from 'aphrodite';
|
212 |
|
213 | // Always keep the full style names
|
214 | minify(false);
|
215 |
|
216 | // ... proceed to use StyleSheet.create etc.
|
217 | ```
|
218 |
|
219 | ## Font Faces
|
220 |
|
221 | Creating custom font faces is a special case. Typically you need to define a global `@font-face` rule. In the case of Aphrodite we only want to insert that rule if it's actually being referenced by a class that's in the page. We've made it so that the `fontFamily` property can accept a font-face object (either directly or inside an array). A global `@font-face` rule is then generated based on the font definition.
|
222 |
|
223 | ```js
|
224 | const coolFont = {
|
225 | fontFamily: "CoolFont",
|
226 | fontStyle: "normal",
|
227 | fontWeight: "normal",
|
228 | src: "url('coolfont.woff2') format('woff2')"
|
229 | };
|
230 |
|
231 | const styles = StyleSheet.create({
|
232 | headingText: {
|
233 | fontFamily: coolFont,
|
234 | fontSize: 20
|
235 | },
|
236 | bodyText: {
|
237 | fontFamily: [coolFont, "sans-serif"]
|
238 | fontSize: 12
|
239 | }
|
240 | });
|
241 | ```
|
242 |
|
243 | Aphrodite will ensure that the global `@font-face` rule for this font is only inserted once, no matter how many times it's referenced.
|
244 |
|
245 | ## Animations
|
246 |
|
247 | Similar to [Font Faces](#font-faces), Aphrodite supports keyframe animations, but it's treated as a special case. Once we find an instance of the animation being referenced, a global `@keyframes` rule is created and appended to the page.
|
248 |
|
249 | Animations are provided as objects describing the animation, in typical `@keyframes` fashion. Using the `animationName` property, you can supply a single animation object, or an array of animation objects. Other animation properties like `animationDuration` can be provided as strings.
|
250 |
|
251 | ```js
|
252 | const translateKeyframes = {
|
253 | '0%': {
|
254 | transform: 'translateX(0)',
|
255 | },
|
256 |
|
257 | '50%': {
|
258 | transform: 'translateX(100px)',
|
259 | },
|
260 |
|
261 | '100%': {
|
262 | transform: 'translateX(0)',
|
263 | },
|
264 | };
|
265 |
|
266 | const opacityKeyframes = {
|
267 | 'from': {
|
268 | opacity: 0,
|
269 | },
|
270 |
|
271 | 'to': {
|
272 | opacity: 1,
|
273 | }
|
274 | };
|
275 |
|
276 | const styles = StyleSheet.create({
|
277 | zippyHeader: {
|
278 | animationName: [translateKeyframes, opacityKeyframes],
|
279 | animationDuration: '3s, 1200ms',
|
280 | animationIterationCount: 'infinite',
|
281 | },
|
282 | });
|
283 | ```
|
284 |
|
285 | Aphrodite will ensure that `@keyframes` rules are never duplicated, no matter how many times a given rule is referenced.
|
286 |
|
287 | # Use without React
|
288 |
|
289 | Aphrodite was built with React in mind but does not depend on React. Here, you can see it
|
290 | used with [Web Components][webcomponents]:
|
291 |
|
292 | ```js
|
293 | import { StyleSheet, css } from 'aphrodite';
|
294 |
|
295 | const styles = StyleSheet.create({
|
296 | red: {
|
297 | backgroundColor: 'red'
|
298 | }
|
299 | });
|
300 |
|
301 | class App extends HTMLElement {
|
302 | attachedCallback() {
|
303 | this.innerHTML = `
|
304 | <div class="${css(styles.red)}">
|
305 | This is red.
|
306 | </div>
|
307 | `;
|
308 | }
|
309 | }
|
310 |
|
311 | document.registerElement('my-app', App);
|
312 | ```
|
313 |
|
314 | # Caveats
|
315 |
|
316 | ## Style injection and buffering
|
317 |
|
318 | Aphrodite will automatically attempt to create a `<style>` tag in the document's `<head>` element to put its generated styles in. Aphrodite will only generate one `<style>` tag and will add new styles to this over time. If you want to control which style tag Aphrodite uses, create a style tag yourself with the `data-aphrodite` attribute and Aphrodite will use that instead of creating one for you.
|
319 |
|
320 | To speed up injection of styles, Aphrodite will automatically try to buffer writes to this `<style>` tag so that minimum number of DOM modifications happen.
|
321 |
|
322 | Aphrodite uses [asap](https://github.com/kriskowal/asap) to schedule buffer flushing. If you measure DOM elements' dimensions in `componentDidMount` or `componentDidUpdate`, you can use `setTimeout` or `flushToStyleTag` to ensure all styles are injected.
|
323 |
|
324 | ```js
|
325 | import { StyleSheet, css } from 'aphrodite';
|
326 |
|
327 | class Component extends React.Component {
|
328 | render() {
|
329 | return <div ref="root" className={css(styles.div)} />;
|
330 | }
|
331 |
|
332 | componentDidMount() {
|
333 | // At this point styles might not be injected yet.
|
334 | this.refs.root.offsetHeight; // 0 or 10
|
335 |
|
336 | setTimeout(() => {
|
337 | this.refs.root.offsetHeight; // 10
|
338 | }, 0);
|
339 | }
|
340 | }
|
341 |
|
342 | const styles = StyleSheet.create({
|
343 | div: {
|
344 | height: 10,
|
345 | },
|
346 | });
|
347 | ```
|
348 |
|
349 | ## Assigning a string to a content property for a pseudo-element
|
350 |
|
351 | When assigning a string to the `content` property it requires double or single quotes in CSS.
|
352 | Therefore with Aphrodite you also have to provide the quotes within the value string for `content` to match how it will be represented in CSS.
|
353 |
|
354 | As an example:
|
355 |
|
356 | ```javascript
|
357 | const styles = StyleSheet.create({
|
358 | large: {
|
359 | ':after': {
|
360 | content: '"Aphrodite"',
|
361 | },
|
362 | },
|
363 | },
|
364 | small: {
|
365 | ':before': {
|
366 | content: "'Aphrodite'",
|
367 | },
|
368 | },
|
369 | });
|
370 | ```
|
371 | The generated css will be:
|
372 |
|
373 | ```css
|
374 | .large_im3wl1:after {
|
375 | content: "Aphrodite" !important;
|
376 | }
|
377 |
|
378 | .small_ffd5jf:before {
|
379 | content: 'Aphrodite' !important;
|
380 | }
|
381 | ```
|
382 |
|
383 | ## Overriding styles
|
384 |
|
385 | When combining multiple aphrodite styles, you are strongly recommended to merge all of your styles into a single call to `css()`, and should not combine the generated class names that aphrodite outputs (via string concatenation, `classnames`, etc.).
|
386 | For example, if you have a base style of `foo` which you are trying to override with `bar`:
|
387 |
|
388 | ### Do this:
|
389 |
|
390 | ```js
|
391 | const styles = StyleSheet.create({
|
392 | foo: {
|
393 | color: 'red'
|
394 | },
|
395 |
|
396 | bar: {
|
397 | color: 'blue'
|
398 | }
|
399 | });
|
400 |
|
401 | // ...
|
402 |
|
403 | const className = css(styles.foo, styles.bar);
|
404 | ```
|
405 |
|
406 | ### Don't do this:
|
407 |
|
408 | ```js
|
409 | const styles = StyleSheet.create({
|
410 | foo: {
|
411 | color: 'red'
|
412 | },
|
413 |
|
414 | bar: {
|
415 | color: 'blue'
|
416 | }
|
417 | });
|
418 |
|
419 | // ...
|
420 |
|
421 | const className = css(styles.foo) + " " + css(styles.bar);
|
422 | ```
|
423 |
|
424 | Why does it matter? Although the second one will produce a valid class name, it cannot guarantee that the `bar` styles will override the `foo` ones.
|
425 | The way the CSS works, it is not the *class name that comes last on an element* that matters, it is specificity. When we look at the generated CSS though, we find that all of the class names have the same specificity, since they are all a single class name:
|
426 |
|
427 | ```css
|
428 | .foo_im3wl1 {
|
429 | color: red;
|
430 | }
|
431 | ```
|
432 |
|
433 | ```css
|
434 | .bar_hxfs3d {
|
435 | color: blue;
|
436 | }
|
437 | ```
|
438 |
|
439 | In the case where the specificity is the same, what matters is *the order that the styles appear in the stylesheet*. That is, if the generated stylesheet looks like
|
440 |
|
441 | ```css
|
442 | .foo_im3wl1 {
|
443 | color: red;
|
444 | }
|
445 | .bar_hxfs3d {
|
446 | color: blue;
|
447 | }
|
448 | ```
|
449 |
|
450 | then you will get the appropriate effect of the `bar` styles overriding the `foo` ones, but if the stylesheet looks like
|
451 |
|
452 | ```css
|
453 | .bar_hxfs3d {
|
454 | color: blue;
|
455 | }
|
456 | .foo_im3wl1 {
|
457 | color: red;
|
458 | }
|
459 | ```
|
460 |
|
461 | then we end up with the opposite effect, with `foo` overriding `bar`! The way to solve this is to pass both of the styles into aphrodite's `css()` call. Then, it will produce a single class name, like `foo_im3wl1-o_O-bar_hxfs3d`, with the correctly overridden styles, thus solving the problem:
|
462 |
|
463 | ```css
|
464 | .foo_im3wl1-o_O-bar_hxfs3d {
|
465 | color: blue;
|
466 | }
|
467 | ```
|
468 |
|
469 | ## Object key ordering
|
470 |
|
471 | When styles are specified in Aphrodite, the order that they appear in the
|
472 | actual stylesheet depends on the order that keys are retrieved from the
|
473 | objects. This ordering is determined by the JavaScript engine that is being
|
474 | used to render the styles. Sometimes, the order that the styles appear in the
|
475 | stylesheet matter for the semantics of the CSS. For instance, depending on the
|
476 | engine, the styles generated from
|
477 |
|
478 | ```js
|
479 | const styles = StyleSheet.create({
|
480 | ordered: {
|
481 | margin: 0,
|
482 | marginLeft: 15,
|
483 | },
|
484 | });
|
485 | css(styles.ordered);
|
486 | ```
|
487 |
|
488 | you might expect the following CSS to be generated:
|
489 |
|
490 | ```css
|
491 | margin: 0px;
|
492 | margin-left: 15px;
|
493 | ```
|
494 |
|
495 | but depending on the ordering of the keys in the style object, the CSS might
|
496 | appear as
|
497 |
|
498 | ```css
|
499 | margin-left: 15px;
|
500 | margin: 0px;
|
501 | ```
|
502 |
|
503 | which is semantically different, because the style which appears later will
|
504 | override the style before it.
|
505 |
|
506 | This might also manifest as a problem when server-side rendering, if the
|
507 | generated styles appear in a different order on the client and on the server.
|
508 |
|
509 | If you experience this issue where styles don't appear in the generated CSS in
|
510 | the order that they appear in your objects, there are two solutions:
|
511 |
|
512 | 1. Don't use shorthand properties. For instance, in the margin example above,
|
513 | by switching from using a shorthand property and a longhand property in the
|
514 | same styles to using only longhand properties, the issue could be avoided.
|
515 |
|
516 | ```js
|
517 | const styles = StyleSheet.create({
|
518 | ordered: {
|
519 | marginTop: 0,
|
520 | marginRight: 0,
|
521 | marginBottom: 0,
|
522 | marginLeft: 15,
|
523 | },
|
524 | });
|
525 | ```
|
526 |
|
527 | 2. Specify the ordering of your styles by specifying them using a
|
528 | [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map).
|
529 | Since `Map`s preserve their insertion order, Aphrodite is able to place your
|
530 | styles in the correct order.
|
531 |
|
532 | ```js
|
533 | const styles = StyleSheet.create({
|
534 | ordered: new Map([
|
535 | ["margin", 0],
|
536 | ["marginLeft", 15],
|
537 | ]),
|
538 | });
|
539 | ```
|
540 |
|
541 | Note that `Map`s are not fully supported in all browsers. It can be
|
542 | polyfilled by using a package
|
543 | like [es6-shim](https://www.npmjs.com/package/es6-shim).
|
544 |
|
545 | ## Advanced: Extensions
|
546 |
|
547 | Extra features can be added to Aphrodite using extensions.
|
548 |
|
549 | To add extensions to Aphrodite, call `StyleSheet.extend` with the extensions
|
550 | you are adding. The result will be an object containing the usual exports of
|
551 | Aphrodite (`css`, `StyleSheet`, etc.) which will have your extensions included.
|
552 | For example:
|
553 |
|
554 | ```js
|
555 | // my-aphrodite.js
|
556 | import {StyleSheet} from "aphrodite";
|
557 |
|
558 | export default StyleSheet.extend([extension1, extension2]);
|
559 |
|
560 | // styled.js
|
561 | import {StyleSheet, css} from "my-aphrodite.js";
|
562 |
|
563 | const styles = StyleSheet.create({
|
564 | ...
|
565 | });
|
566 | ```
|
567 |
|
568 | **Note**: Using extensions may cause Aphrodite's styles to not work properly.
|
569 | Plain Aphrodite, when used properly, ensures that the correct styles will
|
570 | always be applied to elements. Due to CSS specificity rules, extensions might
|
571 | allow you to generate styles that conflict with each other, causing incorrect
|
572 | styles to be shown. See the global extension below to see what could go wrong.
|
573 |
|
574 | ### Creating extensions
|
575 |
|
576 | Currently, there is only one kind of extension available: selector handlers.
|
577 | These kinds of extensions let you look at the selectors that someone specifies
|
578 | and generate new selectors based on them. They are used to handle pseudo-styles
|
579 | and media queries inside of Aphrodite. See the
|
580 | [`defaultSelectorHandlers` docs](src/generate.js?L8) for information about how
|
581 | to create a selector handler function.
|
582 |
|
583 | To use your extension, create an object containing a key of the kind of
|
584 | extension that you created, and pass that into `StyleSheet.extend()`:
|
585 |
|
586 | ```js
|
587 | const mySelectorHandler = ...;
|
588 |
|
589 | const myExtension = {selectorHandler: mySelectorHandler};
|
590 |
|
591 | const { StyleSheet: newStyleSheet, css: newCss } = StyleSheet.extend([myExtension]);
|
592 | ```
|
593 |
|
594 | As an example, you could write an extension which generates global styles like
|
595 |
|
596 | ```js
|
597 | const globalSelectorHandler = (selector, _, generateSubtreeStyles) => {
|
598 | if (selector[0] !== "*") {
|
599 | return null;
|
600 | }
|
601 |
|
602 | return generateSubtreeStyles(selector.slice(1));
|
603 | };
|
604 |
|
605 | const globalExtension = {selectorHandler: globalSelectorHandler};
|
606 | ```
|
607 |
|
608 | This might cause problems when two places try to generate styles for the same
|
609 | global selector however! For example, after
|
610 |
|
611 | ```js
|
612 | const styles = StyleSheet.create({
|
613 | globals: {
|
614 | '*div': {
|
615 | color: 'red',
|
616 | },
|
617 | }
|
618 | });
|
619 |
|
620 | const styles2 = StyleSheet.create({
|
621 | globals: {
|
622 | '*div': {
|
623 | color: 'blue',
|
624 | },
|
625 | },
|
626 | });
|
627 |
|
628 | css(styles.globals);
|
629 | css(styles2.globals);
|
630 | ```
|
631 |
|
632 | It isn't determinate whether divs will be red or blue.
|
633 |
|
634 | ## Minify class names
|
635 |
|
636 | Minify class names by setting the environment variable `process.env.NODE_ENV`
|
637 | to the string value `production`.
|
638 |
|
639 | # Tools
|
640 |
|
641 | - [Aphrodite output tool](https://output.jsbin.com/qoseye) - Paste what you pass to `StyleSheet.create` and see the generated CSS
|
642 | - [jest-aphrodite-react](https://github.com/dmiller9911/jest-aphrodite-react) - Utilities for testing with React and Jest.
|
643 |
|
644 | # TODO
|
645 |
|
646 | - Add JSdoc
|
647 | - Consider removing !important from everything.
|
648 |
|
649 | # Other solutions
|
650 |
|
651 | - [js-next/react-style](https://github.com/js-next/react-style)
|
652 | - [dowjones/react-inline-style](https://github.com/dowjones/react-inline-style)
|
653 | - [martinandert/react-inline](https://github.com/martinandert/react-inline)
|
654 | - [milesj/aesthetic](https://github.com/milesj/aesthetic) - a React style abstraction layer with theme support
|
655 | - [airbnb/react-with-styles](https://github.com/airbnb/react-with-styles)
|
656 |
|
657 | # License (MIT)
|
658 |
|
659 | Copyright (c) 2016 Khan Academy
|
660 |
|
661 | [webcomponents]: http://w3c.github.io/webcomponents/spec/custom
|
662 |
|
663 | [gzip-badge]: http://img.badgesize.io/https://unpkg.com/aphrodite/dist/aphrodite.umd.min.js?compression=gzip&label=gzip%20size
|
664 | [size-badge]: http://img.badgesize.io/https://unpkg.com/aphrodite/dist/aphrodite.umd.min.js?label=size
|
665 | [unpkg-dist]: https://unpkg.com/aphrodite/dist/
|