Repository: microsoft/griffel
Pull request #106 (2022-04-26 12:44:48)

feat(Devtools): add css hightlight and dark theme
- Highlight css with prismjs; matching the color with Edge devtools styles panel.
- Added dark theme. Added story for dark themed FlattenView component
- chore: bump eslint-plugin-react-hooks from 4.2.0 to 4.3.0, which has eslint 8 support. It fixes this [error](https://github.com/facebook/react/issues/22545#issuecomment-941989797)  that I have while disabling react-hooks/exhaustive-deps

<img width="845" alt="Screenshot 2022-05-02 at 17 06 43" src="https://user-images.githubusercontent.com/28751745/166257896-a38de8cf-3e17-4cd6-ae5b-a3d6550ff520.png">
<img width="890" alt="Screenshot 2022-05-02 at 17 06 17" src="https://user-images.githubusercontent.com/28751745/166257884-728aff84-f22c-46b0-bcdd-64ba13460747.png">

<img width="435" alt="Screenshot 2022-04-26 at 13 55 30" src="https://user-images.githubusercontent.com/28751745/165302452-f312aed3-cdb7-4dde-b040-13e846a9411a.png">
<img width="437" alt="Screenshot 2022-04-26 at 13 55 09" src="https://user-images.githubusercontent.com/28751745/165302465-575a346b-c176-49a6-8c23-c6481c48c74c.png">


github-actions[bot]: ## 📊 Bundle size report

<sub>🤖 This report was generated against <a href='https://github.com/microsoft/griffel/commit/3466692fd49ed689121bbd5638bb7072dcaa117a'>3466692fd49ed689121bbd5638bb7072dcaa117a</a></sub>

<!-- Sticky Pull Request Comment -->

Patch:
diff --git a/apps/website/docs/react/api/create-dom-renderer.md b/apps/website/docs/react/api/create-dom-renderer.md
index 8844048..97d6423 100644
--- a/apps/website/docs/react/api/create-dom-renderer.md
+++ b/apps/website/docs/react/api/create-dom-renderer.md
@@ -1,5 +1,5 @@
 ---
-sidebar_position: 4
+sidebar_position: 5
 ---
 
 # createDOMRenderer
diff --git a/apps/website/docs/react/api/make-static-styles.md b/apps/website/docs/react/api/make-static-styles.md
index 35a20bd..98a66cd 100644
--- a/apps/website/docs/react/api/make-static-styles.md
+++ b/apps/website/docs/react/api/make-static-styles.md
@@ -1,5 +1,5 @@
 ---
-sidebar_position: 3
+sidebar_position: 4
 ---
 
 import OutputTitle from '@site/src/components/OutputTitle'
diff --git a/apps/website/docs/react/api/shorthands.md b/apps/website/docs/react/api/shorthands.md
new file mode 100644
index 0000000..cb275e5
--- /dev/null
+++ b/apps/website/docs/react/api/shorthands.md
@@ -0,0 +1,182 @@
+---
+sidebar_position: 3
+---
+
+# shorthands
+
+:::note
+
+Check [limitations](/react/guides/limitations) to understand why these helpers are needed.
+
+:::
+
+`shorthands` provides a set of functions to mimic [CSS shorthands](https://developer.mozilla.org/en-US/docs/Web/CSS/Shorthand_properties) and improve developer experience as [CSS shorthands are not supported](/react/guides/limitations#css-shorthands-are-not-supported) by Griffel. For example:
+
+```tsx
+import { makeStyles, shorthands } from '@griffel/react';
+
+const useClasses = makeStyles({
+  root: {
+    // ❌ This is not supported, TypeScript compiler will throw, styles will not be inserted to DOM
+    padding: '2px 4px 8px 16px',
+    // ✅ Use shorthand functions to avoid writting CSS longhands
+    ...shorthands.padding('2px', '4px', '8px', '16px'),
+  },
+});
+```
+
+:::caution
+
+The most of the functions follow syntax in matching CSS properties, but each value should a separate argument:
+
+```js
+// ❌ Will produce wrong results
+shorthands.padding('2px 4px');
+// ✅ Correct output
+shorthands.padding('2px', '4px');
+```
+
+:::
+
+Function in a `shorthand` set are pure, you can simply use `console.log` to better understand produced rules:
+
+```js
+console.log(padding('12px', '24px', '36px', '48px'));
+// ⬇️⬇️⬇️
+// {
+//  paddingBottom: '36px',
+//  paddingLeft: '48px',
+//  paddingRight: '24px',
+//  paddingTop: '12px',
+// }
+```
+
+### `shorthands.border`
+
+```js
+import { makeStyles, shorthands } from '@griffel/react';
+
+const useClasses = makeStyles({
+  root: {
+    ...shorthands.border('2px'),
+    ...shorthands.border('2px', 'solid'),
+    ...shorthands.border('2px', 'solid', 'red'),
+  },
+});
+```
+
+### `shorthands.borderBottom`
+
+```js
+import { makeStyles, shorthands } from '@griffel/react';
+
+const useClasses = makeStyles({
+  root: {
+    // The same is true for "borderTop", "borderLeft" & "borderRight"
+    ...shorthands.borderBottom('2px'),
+    ...shorthands.borderBottom('2px', 'solid'),
+    ...shorthands.borderBottom('2px', 'solid', 'red'),
+  },
+});
+```
+
+### `shorthands.borderColor`
+
+```js
+import { makeStyles, shorthands } from '@griffel/react';
+
+const useClasses = makeStyles({
+  root: {
+    ...shorthands.borderColor('red'),
+    ...shorthands.borderColor('red', 'blue'),
+    ...shorthands.borderColor('red', 'blue', 'green'),
+    ...shorthands.borderColor('red', 'blue', 'green', 'yellow'),
+  },
+});
+```
+
+### `shorthands.borderStyle`
+
+```js
+import { makeStyles, shorthands } from '@griffel/react';
+
+const useClasses = makeStyles({
+  root: {
+    ...shorthands.borderStyle('solid'),
+    ...shorthands.borderStyle('solid', 'dashed'),
+    ...shorthands.borderStyle('solid', 'dashed', 'dotted'),
+    ...shorthands.borderStyle('solid', 'dashed', 'dotted', 'double'),
+  },
+});
+```
+
+### `shorthands.borderWidth`
+
+```js
+import { makeStyles, shorthands } from '@griffel/react';
+
+const useClasses = makeStyles({
+  root: {
+    ...shorthands.borderWidth('12px'),
+    ...shorthands.borderWidth('12px', '24px'),
+    ...shorthands.borderWidth('12px', '24px', '36px'),
+    ...shorthands.borderWidth('12px', '24px', '36px', '48px'),
+  },
+});
+```
+
+### `shorthands.gap`
+
+```js
+import { makeStyles, shorthands } from '@griffel/react';
+
+const useClasses = makeStyles({
+  root: {
+    ...shorthands.gap('12px'),
+    ...shorthands.gap('12px', '24px'),
+  },
+});
+```
+
+### `shorthands.margin`
+
+```js
+import { makeStyles, shorthands } from '@griffel/react';
+
+const useClasses = makeStyles({
+  root: {
+    ...shorthands.margin('12px'),
+    ...shorthands.margin('12px', '24px'),
+    ...shorthands.margin('12px', '24px', '36px'),
+    ...shorthands.margin('12px', '24px', '36px', '48px'),
+  },
+});
+```
+
+### `shorthands.overflow`
+
+```js
+import { makeStyles, shorthands } from '@griffel/react';
+
+const useClasses = makeStyles({
+  root: {
+    ...shorthands.overflow('visible'),
+    ...shorthands.overflow('visible', 'hidden'),
+  },
+});
+```
+
+### `shorthands.padding`
+
+```js
+import { makeStyles, shorthands } from '@griffel/react';
+
+const useClasses = makeStyles({
+  root: {
+    ...shorthands.padding('12px'),
+    ...shorthands.padding('12px', '24px'),
+    ...shorthands.padding('12px', '24px', '36px'),
+    ...shorthands.padding('12px', '24px', '36px', '48px'),
+  },
+});
+```
diff --git a/apps/website/docs/react/guides/atomic-css.md b/apps/website/docs/react/guides/atomic-css.md
index 0e1b46a..cec1fe7 100644
--- a/apps/website/docs/react/guides/atomic-css.md
+++ b/apps/website/docs/react/guides/atomic-css.md
@@ -3,3 +3,5 @@ sidebar_position: 1
 ---
 
 # Atomic CSS
+
+This page is not ready yet.
diff --git a/apps/website/docs/react/guides/build-time-transforms.md b/apps/website/docs/react/guides/build-time-transforms.md
index 8da446e..b916048 100644
--- a/apps/website/docs/react/guides/build-time-transforms.md
+++ b/apps/website/docs/react/guides/build-time-transforms.md
@@ -3,3 +3,5 @@ sidebar_position: 3
 ---
 
 # Build time transforms
+
+This page is not ready yet.
diff --git a/apps/website/docs/react/guides/limitations.md b/apps/website/docs/react/guides/limitations.md
index 033ebbb..6c50bed 100644
--- a/apps/website/docs/react/guides/limitations.md
+++ b/apps/website/docs/react/guides/limitations.md
@@ -3,3 +3,88 @@ sidebar_position: 2
 ---
 
 # Limitations
+
+## CSS shorthands are not supported
+
+There are [shorthand and longhand properties](https://developer.mozilla.org/en-US/docs/Web/CSS/Shorthand_properties) in CSS. Shorthand properties allow to define a set of longhand properties. For example:
+
+```css
+/* Define with multiple properties */
+.longhand-rule {
+  padding-top: 2px;
+  padding-right: 4px;
+  padding-bottom: 8px;
+  padding-left: 16px;
+}
+
+/* Define with a single property */
+.shorthand-rule {
+  padding: 2px 4px 8px 16px;
+}
+```
+
+Griffel relies on [Atomic CSS](/react/guides/atomic-css) and produces atomic classes:
+
+```css
+/* 💡 makeStyles() generates hashed classes, but it's not important in this example */
+.a {
+  background-color: red;
+}
+.b {
+  background-color: green;
+}
+.c {
+  color: yellow;
+}
+```
+
+```html
+<!-- Case 1: ❌ Wrong usage -->
+<div class="a b c">Hello world!</div>
+<!-- Case 2: ✅ Correct usage -->
+<div class="a c">Hello world!</div>
+<div class="b c">Hello world!</div>
+```
+
+- In "Case 1" both classes are applied to an element: it's wrong as result is **nondeterministic** and depends on classes order in CSS definitions (i.e. [order of appearance](https://www.w3.org/TR/css-cascade-3/#cascade-order)), [demo on CodeSandbox](https://codesandbox.io/s/css-insertion-order-matters-mgt6y)
+- In "Case 2" only single classname per CSS property is applied, result will be deterministic
+
+This problem is solved by [`mergeClasses()`](https://github.com/microsoft/griffel/blob/main/packages/core/src/mergeClasses.ts) function: it de-duplicates classes based on property name.
+
+```jsx
+// ⚠ Simplified example
+function App() {
+  //                     👇 skips "a", returns only "b c"
+  return <div className={mergeClasses('a', 'b', 'c')}>Hello world</div>;
+}
+```
+
+> Only non colliding properties should be applied to DOM elements with Atomic CSS.
+
+This works well for longhands, but there are cases when longhands and shorthands are combined:
+
+```js
+// ⚠ Not real code
+const useClasses1 = makeStyles({
+  root: {
+    backgroundColor: 'red',
+    background: 'green',
+  },
+});
+const useClasses2 = makeStyles({
+  root: {
+    background: 'green',
+    backgroundColor: 'red',
+  },
+});
+```
+
+:::caution
+
+In this example the problem is the same: both classes will be applied, result depends on the order of appearance.
+
+:::
+
+There is an option to expand CSS shorthands to longhands, but it's not reliable and does not work with static rules i.e. you can't expand rules with CSS variables without knowing their value. The single predictable solution is to disallow the usage of CSS shorthands.
+
+You can get more information on the original RFC, [microsoft/fluentui#20573](https://github.com/microsoft/fluentui/pull/20573). For this reason Griffel disallows CSS shorthand properties in the input style object. Instead of shorthand properties, use [shorthands helper functions](/react/api/shorthands) which do the shorthand to longhand expansion statically.
diff --git a/apps/website/docs/react/guides/ssr-usage.md b/apps/website/docs/react/guides/ssr-usage.md
index 033b16e..8fe7693 100644
--- a/apps/website/docs/react/guides/ssr-usage.md
+++ b/apps/website/docs/react/guides/ssr-usage.md
@@ -21,12 +21,14 @@ A complete demo project is available on [CodeSandbox](https://codesandbox.io/s/n
 
 1. Create a `_document.js` file under your `pages` folder with the following content:
 
-```tsx
+```jsx
+// highlight-next-line
 import { createDOMRenderer, renderToStyleElements } from '@griffel/react';
 import Document, { Html, Head, Main, NextScript } from 'next/document';
 
 class MyDocument extends Document {
   static async getInitialProps(ctx) {
+    // highlight-start
     // 👇 creates a renderer
     const renderer = createDOMRenderer();
     const originalRenderPage = ctx.renderPage;
@@ -35,8 +37,10 @@ class MyDocument extends Document {
       originalRenderPage({
         enhanceApp: App => props => <App {...props} renderer={renderer} />,
       });
+    // highlight-end
 
     const initialProps = await Document.getInitialProps(ctx);
+    // highlight-start
     const styles = renderToStyleElements(renderer);
 
     return {
@@ -44,6 +48,7 @@ class MyDocument extends Document {
       // 👇 adding our styles elements to output
       styles: [...initialProps.styles, ...styles],
     };
+    // highlight-end
   }
 
   render() {
diff --git a/change/@griffel-core-8e16f578-f749-4f26-9899-b90b14811104.json b/change/@griffel-core-8e16f578-f749-4f26-9899-b90b14811104.json
new file mode 100644
index 0000000..75ee078
--- /dev/null
+++ b/change/@griffel-core-8e16f578-f749-4f26-9899-b90b14811104.json
@@ -0,0 +1,7 @@
+{
+  "type": "minor",
+  "comment": "Inject devtools when session storage contains __GRIFFEL_DEVTOOLS__",
+  "packageName": "@griffel/core",
+  "email": "yuanboxue@microsoft.com",
+  "dependentChangeType": "patch"
+}
diff --git a/change/@griffel-core-94a0ec59-abfa-4d0f-a8a8-843026c57848.json b/change/@griffel-core-94a0ec59-abfa-4d0f-a8a8-843026c57848.json
new file mode 100644
index 0000000..c5fd055
--- /dev/null
+++ b/change/@griffel-core-94a0ec59-abfa-4d0f-a8a8-843026c57848.json
@@ -0,0 +1,7 @@
+{
+  "type": "patch",
+  "comment": "fix issue with comma-separated selectors and RTL",
+  "packageName": "@griffel/core",
+  "email": "seanmonahan@microsoft.com",
+  "dependentChangeType": "patch"
+}
diff --git a/change/@griffel-core-ad30b8f8-c6c7-481e-be0d-2e8268b29fdd.json b/change/@griffel-core-ad30b8f8-c6c7-481e-be0d-2e8268b29fdd.json
new file mode 100644
index 0000000..1e841dd
--- /dev/null
+++ b/change/@griffel-core-ad30b8f8-c6c7-481e-be0d-2e8268b29fdd.json
@@ -0,0 +1,7 @@
+{
+  "type": "minor",
+  "comment": "feat: add shorthand function for outline",
+  "packageName": "@griffel/core",
+  "email": "bsunderhus@microsoft.com",
+  "dependentChangeType": "patch"
+}
diff --git a/change/@griffel-core-cbfaaa86-8f9f-4b6d-bbbc-97a91bc628f3.json b/change/@griffel-core-cbfaaa86-8f9f-4b6d-bbbc-97a91bc628f3.json
new file mode 100644
index 0000000..545b27a
--- /dev/null
+++ b/change/@griffel-core-cbfaaa86-8f9f-4b6d-bbbc-97a91bc628f3.json
@@ -0,0 +1,7 @@
+{
+  "type": "none",
+  "comment": "README update",
+  "packageName": "@griffel/core",
+  "email": "olfedias@microsoft.com",
+  "dependentChangeType": "none"
+}
diff --git a/change/@griffel-react-bdfe2a55-bc15-43bf-8c29-e63d7ac1b1d5.json b/change/@griffel-react-bdfe2a55-bc15-43bf-8c29-e63d7ac1b1d5.json
new file mode 100644
index 0000000..3ba246f
--- /dev/null
+++ b/change/@griffel-react-bdfe2a55-bc15-43bf-8c29-e63d7ac1b1d5.json
@@ -0,0 +1,7 @@
+{
+  "type": "none",
+  "comment": "README update",
+  "packageName": "@griffel/react",
+  "email": "olfedias@microsoft.com",
+  "dependentChangeType": "none"
+}
diff --git a/package.json b/package.json
index 0cfdb04..0a2d138 100644
--- a/package.json
+++ b/package.json
@@ -74,7 +74,7 @@
     "eslint-plugin-import": "2.25.2",
     "eslint-plugin-jsx-a11y": "6.5.1",
     "eslint-plugin-react": "7.27.0",
-    "eslint-plugin-react-hooks": "4.2.0",
+    "eslint-plugin-react-hooks": "4.3.0",
     "highlight.js": "11.4.0",
     "jest": "27.2.3",
     "js-beautify": "^1.14.0",
diff --git a/packages/core/README.md b/packages/core/README.md
index 4291d91..c219b79 100644
--- a/packages/core/README.md
+++ b/packages/core/README.md
@@ -1,3 +1,3 @@
 # `@griffel/core`
 
-DOM implementation of Griffel.
+DOM implementation of Griffel. Do not use this package directly, please use [`@griffel/react`](../react) instead.
diff --git a/packages/core/src/devtools/isDevToolsEnabled.ts b/packages/core/src/devtools/isDevToolsEnabled.ts
index 44c21d9..77bd1fa 100644
--- a/packages/core/src/devtools/isDevToolsEnabled.ts
+++ b/packages/core/src/devtools/isDevToolsEnabled.ts
@@ -1,1 +1,3 @@
-export const isDevToolsEnabled = false;
+export const isDevToolsEnabled = Boolean(
+  typeof window !== 'undefined' && window?.sessionStorage?.getItem('__GRIFFEL_DEVTOOLS__'),
+);
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 4eab908..d432dfc 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -15,6 +15,7 @@ import {
   padding,
   overflow,
   inset,
+  outline,
 } from './shorthands/index';
 
 export const shorthands = {
@@ -32,6 +33,7 @@ export const shorthands = {
   padding,
   overflow,
   inset,
+  outline,
 };
 
 export { createDOMRenderer } from './renderer/createDOMRenderer';
diff --git a/packages/core/src/runtime/compileCSS.ts b/packages/core/src/runtime/compileCSS.ts
index 11adaeb..ab16ded 100644
--- a/packages/core/src/runtime/compileCSS.ts
+++ b/packages/core/src/runtime/compileCSS.ts
@@ -102,7 +102,7 @@ export function compileCSS(options: CompileCSSOptions): [string /* ltr definitio
     cssRule = `${classNameSelector}{${normalizedPseudo} ${cssDeclaration}};`;
 
     if (rtlProperty) {
-      cssRule = `${cssRule}; ${rtlClassNameSelector}${normalizedPseudo} ${rtlCSSDeclaration};`;
+      cssRule = `${cssRule}; ${rtlClassNameSelector}{${normalizedPseudo} ${rtlCSSDeclaration}};`;
     }
   }
 
diff --git a/packages/core/src/shorthands/index.ts b/packages/core/src/shorthands/index.ts
index b398d95..58d84c8 100644
--- a/packages/core/src/shorthands/index.ts
+++ b/packages/core/src/shorthands/index.ts
@@ -12,3 +12,4 @@ export { margin } from './margin';
 export { padding } from './padding';
 export { overflow } from './overflow';
 export { inset } from './inset';
+export { outline } from './outline';
diff --git a/packages/core/src/shorthands/outline.ts b/packages/core/src/shorthands/outline.ts
new file mode 100644
index 0000000..8f23efa
--- /dev/null
+++ b/packages/core/src/shorthands/outline.ts
@@ -0,0 +1,31 @@
+import type { GriffelStylesStrictCSSObject } from '../types';
+import { OutlineColorInput, OutlineStyleInput, OutlineWidthInput } from './types';
+
+type OutlineStyle = Pick<GriffelStylesStrictCSSObject, 'outlineColor' | 'outlineStyle' | 'outlineWidth'>;
+
+export function outline(width: OutlineWidthInput): OutlineStyle;
+export function outline(width: OutlineWidthInput, style: OutlineStyleInput): OutlineStyle;
+export function outline(width: OutlineWidthInput, style: OutlineStyleInput, color: OutlineColorInput): OutlineStyle;
+
+/**
+ * A function that implements expansion for "outline", it's simplified - check usage examples.
+ *
+ * @example
+ *  outline('2px')
+ *  outline('2px', 'solid')
+ *  outline('2px', 'solid', 'red')
+ *
+ * See https://developer.mozilla.org/en-US/docs/Web/CSS/outline
+ *
+ */
+export function outline(
+  outlineWidth: OutlineWidthInput,
+  outlineStyle?: OutlineStyleInput,
+  outlineColor?: OutlineColorInput,
+): OutlineStyle {
+  return {
+    outlineWidth,
+    ...(outlineStyle && { outlineStyle }),
+    ...(outlineColor && { outlineColor }),
+  };
+}
diff --git a/packages/core/src/shorthands/types.ts b/packages/core/src/shorthands/types.ts
index 2ac2eb6..ee8b706 100644
--- a/packages/core/src/shorthands/types.ts
+++ b/packages/core/src/shorthands/types.ts
@@ -5,6 +5,10 @@ export type BorderWidthInput = ValueOrArray<CSS.Property.BorderWidth<GriffelStyl
 export type BorderStyleInput = ValueOrArray<CSS.Property.BorderStyle>;
 export type BorderColorInput = ValueOrArray<CSS.Property.BorderColor>;
 
+export type OutlineWidthInput = ValueOrArray<CSS.Property.OutlineWidth<GriffelStylesCSSValue>>;
+export type OutlineStyleInput = ValueOrArray<CSS.Property.OutlineStyle>;
+export type OutlineColorInput = ValueOrArray<CSS.Property.OutlineColor>;
+
 export type BorderRadiusInput = ValueOrArray<CSS.Property.BorderRadius<GriffelStylesCSSValue>>;
 
 export type GapInput = ValueOrArray<CSS.Property.Gap<GriffelStylesCSSValue>>;
diff --git a/packages/devtools/package.json b/packages/devtools/package.json
index 7b5200a..61e2b9f 100644
--- a/packages/devtools/package.json
+++ b/packages/devtools/package.json
@@ -12,8 +12,9 @@
   "dependencies": {
     "@griffel/react": "^1.0.3",
     "js-beautify": "^1.14.0",
-    "react": ">=16.8.0 <18.0.0",
+    "prism-react-renderer": "1.2.1", 
     "react-dom": ">=16.8.0 <18.0.0",
+    "react": ">=16.8.0 <18.0.0",
     "stylis": "^4.0.13"
   }
 }
diff --git a/packages/devtools/public/content-script.js b/packages/devtools/public/content-script.js
new file mode 100644
index 0000000..9cd0139
--- /dev/null
+++ b/packages/devtools/public/content-script.js
@@ -0,0 +1,1 @@
+window.sessionStorage.setItem('__GRIFFEL_DEVTOOLS__', 'enabled');
diff --git a/packages/devtools/public/manifest.json b/packages/devtools/public/manifest.json
index 36daa35..79f93a8 100644
--- a/packages/devtools/public/manifest.json
+++ b/packages/devtools/public/manifest.json
@@ -3,5 +3,13 @@
   "manifest_version": 3,
   "name": "griffel-devtools",
   "version": "1.0",
-  "devtools_page": "devtools-page.html"
+  "devtools_page": "devtools-page.html",
+  "content_scripts": [
+    {
+      "matches": ["<all_urls>"],
+      "js": ["content-script.js"],
+      "all_frames": true,
+      "run_at": "document_start"
+    }
+  ]
 }
diff --git a/packages/devtools/src/DefaultMessage.tsx b/packages/devtools/src/DefaultMessage.tsx
index 5b06cbd..b6d492a 100644
--- a/packages/devtools/src/DefaultMessage.tsx
+++ b/packages/devtools/src/DefaultMessage.tsx
@@ -6,12 +6,13 @@ const useStyles = makeStyles({
     display: 'grid',
     gridTemplateColumns: 'auto 1fr',
     gridGap: '3px',
-    ...shorthands.margin('5px'),
+    ...shorthands.padding('5px'),
   },
   icon: {
     textDecorationLine: 'none',
     position: 'relative',
     top: '-5px',
+    zIndex: 1,
   },
   text: {
     '::after': {
@@ -24,7 +25,6 @@ const useStyles = makeStyles({
       position: 'relative',
       top: 'calc(-100%)',
       left: '-10px',
-      zIndex: -1,
       ...shorthands.border('1px', 'dotted', 'deepskyblue'),
     },
   },
diff --git a/packages/devtools/src/FlattenView.tsx b/packages/devtools/src/FlattenView.tsx
index 5cc86dc..3ed55ac 100644
--- a/packages/devtools/src/FlattenView.tsx
+++ b/packages/devtools/src/FlattenView.tsx
@@ -3,12 +3,15 @@ import { makeStaticStyles, makeStyles, shorthands } from '@griffel/react';
 
 import { SlotCSSRules } from './SlotCSSRules';
 import { getRulesBySlots } from './utils';
+import { tokens } from './themes';
 import { ViewContext } from './ViewContext';
 
 import type { DebugResult } from '@griffel/core';
 
 const useStyles = makeStyles({
   root: {
+    backgroundColor: tokens.background,
+    color: tokens.foreground,
     paddingBottom: '10px',
   },
   info: {
diff --git a/packages/devtools/src/HighlightedCSS.tsx b/packages/devtools/src/HighlightedCSS.tsx
new file mode 100644
index 0000000..cb899f0
--- /dev/null
+++ b/packages/devtools/src/HighlightedCSS.tsx
@@ -0,0 +1,96 @@
+import { makeStyles, shorthands } from '@griffel/react';
+import Highlight, { defaultProps, PrismTheme } from 'prism-react-renderer';
+import * as React from 'react';
+
+import { tokens } from './themes';
+
+const customTheme: PrismTheme = {
+  plain: {
+    color: tokens.foreground,
+  },
+  styles: [
+    {
+      types: ['keyword', 'atrule'],
+      style: {
+        color: tokens.foreground,
+      },
+    },
+    {
+      types: ['rule'],
+      style: {
+        color: tokens.cssAtRule,
+      },
+    },
+    {
+      types: ['property'],
+      style: {
+        color: tokens.cssProperty,
+      },
+    },
+    {
+      types: ['punctuation'],
+      style: {
+        color: tokens.cssPunctuation,
+      },
+    },
+    {
+      types: ['selector'],
+      style: {
+        color: tokens.cssSelector,
+      },
+    },
+    {
+      types: ['number'],
+      style: {
+        color: tokens.cssNumber,
+      },
+    },
+  ],
+};
+
+const useColorIndicatorStyles = makeStyles({
+  colorIndicator: {
+    boxSizing: 'border-box',
+    height: '12px',
+    width: '12px',
+    display: 'inline-block',
+    marginLeft: '2px',
+    marginRight: '2px',
+    verticalAlign: 'text-top',
+    ...shorthands.border('1px', 'dotted', 'grey'),
+  },
+});
+
+export const HighlightedCSS: React.FC<{ code: string }> = ({ code }) => {
+  const { colorIndicator } = useColorIndicatorStyles();
+
+  return (
+    <Highlight {...defaultProps} code={code} language="css" theme={customTheme}>
+      {({ className, style, tokens, getLineProps, getTokenProps }) => (
+        <pre className={className} style={{ ...style, display: 'inline-block', textDecorationLine: 'inherit' }}>
+          {tokens.map((line, i) => (
+            <div {...getLineProps({ line, key: i })}>
+              {line.map((token, key) => {
+                const tokenProps = getTokenProps({ token, key });
+                return tokenProps.className.includes('color') && token.content !== 'transparent' ? (
+                  <span
+                    {...tokenProps}
+                    children={
+                      <>
+                        {/* show a square color indicator for colors in css */}
+                        <span className={colorIndicator} style={{ backgroundColor: token.content }} />
+                        <span>{tokenProps.children}</span>
+                      </>
+                    }
+                  />
+                ) : (
+                  <span {...tokenProps} />
+                );
+              })}
+            </div>
+          ))}
+        </pre>
+      )}
+    </Highlight>
+  );
+};
diff --git a/packages/devtools/src/MonolithicRulesView.tsx b/packages/devtools/src/MonolithicRulesView.tsx
index 960422a..e53e678 100644
--- a/packages/devtools/src/MonolithicRulesView.tsx
+++ b/packages/devtools/src/MonolithicRulesView.tsx
@@ -2,6 +2,7 @@ import beautify from 'js-beautify';
 import * as React from 'react';
 import { makeStyles, mergeClasses, shorthands } from '@griffel/react';
 
+import { HighlightedCSS } from './HighlightedCSS';
 import { useViewContext } from './ViewContext';
 import type { MonolithicRules, RuleDetail } from './types';
 
@@ -47,7 +48,7 @@ const SingleRuleView: React.FC<{ rule: RuleDetail; indent?: boolean }> = ({ rule
 
   return (
     <div onClick={handleClick} className={className}>
-      <pre>{formatCSS(rule.css)}</pre>
+      <HighlightedCSS code={formatCSS(rule.css)} />
     </div>
   );
 };
@@ -79,7 +80,7 @@ export const MonolithicRulesView: React.FC<MonolithicRulesViewProps> = props =>
       {Object.entries(rules).map(([selector, rules]) => {
         return (
           <div key={selector} className={noLineSpacing ? classes.noLineSpacing : classes.lineSpacing}>
-            {selector && <pre>{`${selector} {`} </pre>}
+            {selector && <HighlightedCSS code={`${selector} {`} />}
             {Array.isArray(rules) ? (
               rules.map(rule => <SingleRuleView key={rule.css} rule={rule} indent={!!selector} />)
             ) : (
@@ -87,7 +88,7 @@ export const MonolithicRulesView: React.FC<MonolithicRulesViewProps> = props =>
                 <MonolithicRulesView rules={rules} noLineSpacing />
               </div>
             )}
-            {selector && <pre>{`}`} </pre>}
+            {selector && <HighlightedCSS code={`}`} />}
           </div>
         );
       })}
diff --git a/packages/devtools/src/SlotCSSRules.tsx b/packages/devtools/src/SlotCSSRules.tsx
index 6637b8b..f9b1f8e 100644
--- a/packages/devtools/src/SlotCSSRules.tsx
+++ b/packages/devtools/src/SlotCSSRules.tsx
@@ -3,6 +3,7 @@ import { makeStyles, shorthands } from '@griffel/react';
 
 import { getMonolithicCSSRules } from './getMonolithicCSSRules';
 import { MonolithicRulesView } from './MonolithicRulesView';
+import { tokens } from './themes';
 import { useViewContext } from './ViewContext';
 
 import type { AtomicRules } from './types';
@@ -11,8 +12,9 @@ const useStyles = makeStyles({
   slotName: {
     ...shorthands.padding('2px', '5px'),
     ...shorthands.margin('5px', 0),
-    ...shorthands.borderTop('1px', 'solid', 'grey'),
-    ...shorthands.borderBottom('1px', 'solid', 'grey'),
+    ...shorthands.borderTop('1px', 'solid', tokens.slotNameBorder),
+    ...shorthands.borderBottom('1px', 'solid', tokens.slotNameBorder),
+    backgroundColor: tokens.slotNameBackground,
   },
   rules: {
     ...shorthands.padding(0, '5px'),
@@ -21,7 +23,6 @@ const useStyles = makeStyles({
 
 export const SlotCSSRules: React.FC<{ slot: string; atomicRules: AtomicRules[] }> = ({ slot, atomicRules }) => {
   const rules = React.useMemo(() => getMonolithicCSSRules(atomicRules), [atomicRules]);
-
   const classes = useStyles();
 
   const { setHighlightedClass } = useViewContext();
diff --git a/packages/devtools/src/index.html b/packages/devtools/src/index.html
index 2d1e7de..451858e 100644
--- a/packages/devtools/src/index.html
+++ b/packages/devtools/src/index.html
@@ -13,6 +13,11 @@
         padding: 0;
         margin: 0;
       }
+
+      #root {
+        height: inherit;
+        width: inherit;
+      }
     </style>
   </head>
   <body>
diff --git a/packages/devtools/src/index.tsx b/packages/devtools/src/index.tsx
index 06f63b0..1b3600d 100644
--- a/packages/devtools/src/index.tsx
+++ b/packages/devtools/src/index.tsx
@@ -3,16 +3,32 @@ import * as ReactDOM from 'react-dom';
 
 import { DefaultMessage } from './DefaultMessage';
 import { FlattenView } from './FlattenView';
+import { darkTheme, lightTheme, tokens } from './themes';
 import { useMakeStylesState } from './useMakeStylesState';
 
 const DevTools: React.FC = () => {
   const state = useMakeStylesState();
+  const theme = chrome.devtools?.panels?.themeName === 'dark' ? darkTheme : lightTheme;
+
+  let children = <DefaultMessage />;
 
   if (state && Object.keys(state).length > 0) {
-    return <FlattenView debugResultRoot={state} />;
+    children = <FlattenView debugResultRoot={state} />;
   }
 
-  return <DefaultMessage />;
+  return (
+    <div
+      style={{
+        ...(theme as React.CSSProperties),
+        color: tokens.foreground,
+        backgroundColor: tokens.background,
+        height: 'inherit',
+        width: 'inherit',
+      }}
+    >
+      {children}
+    </div>
+  );
 };
 
 ReactDOM.render(
diff --git a/packages/devtools/src/stories/FlattenView.stories.tsx b/packages/devtools/src/stories/FlattenView.stories.tsx
index 3d17e90..7949b18 100644
--- a/packages/devtools/src/stories/FlattenView.stories.tsx
+++ b/packages/devtools/src/stories/FlattenView.stories.tsx
@@ -1,7 +1,10 @@
 import * as React from 'react';
-import { FlattenView } from '../FlattenView';
+import { Story } from '@storybook/react';
 import type { DebugResult } from '@griffel/core';
 
+import { FlattenView } from '../FlattenView';
+import { darkTheme, lightTheme } from '../themes';
+
 const debugResultRoot: DebugResult = {
   sequenceHash: '___29aowz0_1rg0tlg',
   direction: 'ltr',
@@ -93,12 +96,32 @@ const debugResultRoot: DebugResult = {
   ],
 };
 
-export const Default = () => (
-  <div style={{ border: '3px solid gray', width: 400 }}>
-    <FlattenView debugResultRoot={debugResultRoot} />
-  </div>
-);
+export const Default: Story<{ theme: 'dark' | 'light' }> = ({ theme }) => {
+  const tokens = theme === 'dark' ? darkTheme : lightTheme;
+
+  return (
+    <div
+      style={{
+        ...tokens,
+        border: '3px solid gray',
+        width: 400,
+      }}
+    >
+      <FlattenView debugResultRoot={debugResultRoot} />
+    </div>
+  );
+};
+
+Default.args = {
+  theme: 'light',
+};
 
 export default {
   title: 'FlattenView',
+  argTypes: {
+    theme: {
+      options: ['light', 'dark'],
+      control: { type: 'radio' },
+    },
+  },
 };
diff --git a/packages/devtools/src/themes.ts b/packages/devtools/src/themes.ts
new file mode 100644
index 0000000..cb89dde
--- /dev/null
+++ b/packages/devtools/src/themes.ts
@@ -0,0 +1,51 @@
+import type { ColorTokens } from './types';
+
+export const tokens: Record<keyof ColorTokens, `var(--griffel-${keyof ColorTokens})`> = {
+  foreground: 'var(--griffel-foreground)',
+  background: 'var(--griffel-background)',
+
+  cssAtRule: 'var(--griffel-cssAtRule)',
+  cssNumber: 'var(--griffel-cssNumber)',
+  cssProperty: 'var(--griffel-cssProperty)',
+  cssPunctuation: 'var(--griffel-cssPunctuation)',
+  cssSelector: 'var(--griffel-cssSelector)',
+
+  slotNameBackground: 'var(--griffel-slotNameBackground)',
+  slotNameBorder: 'var(--griffel-slotNameBorder)',
+};
+
+function themeToVariables(theme: ColorTokens) {
+  return Object.fromEntries(
+    Object.entries(theme).map(([token, value]) => {
+      return [`--griffel-${token}`, value];
+    }),
+  );
+}
+
+export const lightTheme = themeToVariables({
+  foreground: 'rgb(34, 34, 34)',
+  background: 'white',
+
+  cssAtRule: 'rgb(128, 134, 139)',
+  cssNumber: 'rgb(9, 134, 88)',
+  cssProperty: 'rgb(19, 44, 121)',
+  cssPunctuation: 'rgb(102, 102, 102)',
+  cssSelector: 'rgb(158, 51, 121)',
+
+  slotNameBackground: 'rgb(247, 247, 247)',
+  slotNameBorder: 'rgb(202, 205, 209)',
+});
+
+export const darkTheme = themeToVariables({
+  background: 'rgb(32, 33, 36)',
+  foreground: 'rgb(213, 213, 213)',
+
+  cssAtRule: 'rgb(128, 134, 139)',
+  cssNumber: 'rgb(181, 206, 168)',
+  cssProperty: 'rgb(156, 220, 254)',
+  cssPunctuation: 'rgb(167, 167, 167)',
+  cssSelector: 'rgb(175, 165, 143)',
+
+  slotNameBackground: 'rgb(51, 51, 51)',
+  slotNameBorder: 'rgb(73, 76, 80)',
+});
diff --git a/packages/devtools/src/types.ts b/packages/devtools/src/types.ts
index d8a4796..80b4804 100644
--- a/packages/devtools/src/types.ts
+++ b/packages/devtools/src/types.ts
@@ -1,3 +1,18 @@
+export type ColorTokens = {
+  foreground: string;
+  background: string;
+
+  slotNameBackground: string;
+  slotNameBorder: string;
+
+  /** tokens for css highlighting */
+  cssAtRule: string;
+  cssNumber: string;
+  cssProperty: string;
+  cssPunctuation: string;
+  cssSelector: string;
+};
+
 export type SlotInfo = { slot: string; rules: AtomicRules[] };
 
 export type AtomicRules = { cssRule: string; overriddenBy?: string };
diff --git a/packages/devtools/tsconfig.json b/packages/devtools/tsconfig.json
index c63e275..846439c 100644
--- a/packages/devtools/tsconfig.json
+++ b/packages/devtools/tsconfig.json
@@ -1,6 +1,8 @@
 {
   "extends": "../../tsconfig.base.json",
   "compilerOptions": {
+    "target": "es2019",
+    "lib": ["es2019", "dom"],
     "jsx": "react",
     "allowJs": true,
     "esModuleInterop": true,
diff --git a/packages/react/README.md b/packages/react/README.md
index 8f1d3db..9f55a30 100644
--- a/packages/react/README.md
+++ b/packages/react/README.md
@@ -70,6 +70,140 @@ function Component() {
 }
 ```
 
+## `shorthands`
+
+`shorthands` provides a set of functions to mimic [CSS shorthands](https://developer.mozilla.org/en-US/docs/Web/CSS/Shorthand_properties) and improve developer experience as [CSS shorthands are not supported](https://griffel.js.org/react/guides/limitations#css-shorthands-are-not-supported) by Griffel.
+
+### `shorthands.border`
+
+```js
+import { makeStyles, shorthands } from '@griffel/react';
+
+const useClasses = makeStyles({
+  root: {
+    ...shorthands.border('2px'),
+    ...shorthands.border('2px', 'solid'),
+    ...shorthands.border('2px', 'solid', 'red'),
+  },
+});
+```
+
+### `shorthands.borderBottom`, `shorthands.borderTop`, `shorthands.borderLeft`, `shorthands.borderRight`
+
+```js
+import { makeStyles, shorthands } from '@griffel/react';
+
+const useClasses = makeStyles({
+  root: {
+    // The same is true for "borderTop", "borderLeft" & "borderRight"
+    ...shorthands.borderBottom('2px'),
+    ...shorthands.borderBottom('2px', 'solid'),
+    ...shorthands.borderBottom('2px', 'solid', 'red'),
+  },
+});
+```
+
+### `shorthands.borderColor`
+
+```js
+import { makeStyles, shorthands } from '@griffel/react';
+
+const useClasses = makeStyles({
+  root: {
+    ...shorthands.borderColor('red'),
+    ...shorthands.borderColor('red', 'blue'),
+    ...shorthands.borderColor('red', 'blue', 'green'),
+    ...shorthands.borderColor('red', 'blue', 'green', 'yellow'),
+  },
+});
+```
+
+### `shorthands.borderStyle`
+
+```js
+import { makeStyles, shorthands } from '@griffel/react';
+
+const useClasses = makeStyles({
+  root: {
+    ...shorthands.borderStyle('solid'),
+    ...shorthands.borderStyle('solid', 'dashed'),
+    ...shorthands.borderStyle('solid', 'dashed', 'dotted'),
+    ...shorthands.borderStyle('solid', 'dashed', 'dotted', 'double'),
+  },
+});
+```
+
+### `shorthands.borderWidth`
+
+```js
+import { makeStyles, shorthands } from '@griffel/react';
+
+const useClasses = makeStyles({
+  root: {
+    ...shorthands.borderWidth('12px'),
+    ...shorthands.borderWidth('12px', '24px'),
+    ...shorthands.borderWidth('12px', '24px', '36px'),
+    ...shorthands.borderWidth('12px', '24px', '36px', '48px'),
+  },
+});
+```
+
+### `shorthands.gap`
+
+```js
+import { makeStyles, shorthands } from '@griffel/react';
+
+const useClasses = makeStyles({
+  root: {
+    ...shorthands.gap('12px'),
+    ...shorthands.gap('12px', '24px'),
+  },
+});
+```
+
+### `shorthands.margin`
+
+```js
+import { makeStyles, shorthands } from '@griffel/react';
+
+const useClasses = makeStyles({
+  root: {
+    ...shorthands.margin('12px'),
+    ...shorthands.margin('12px', '24px'),
+    ...shorthands.margin('12px', '24px', '36px'),
+    ...shorthands.margin('12px', '24px', '36px', '48px'),
+  },
+});
+```
+
+### `shorthands.overflow`
+
+```js
+import { makeStyles, shorthands } from '@griffel/react';
+
+const useClasses = makeStyles({
+  root: {
+    ...shorthands.overflow('visible'),
+    ...shorthands.overflow('visible', 'hidden'),
+  },
+});
+```
+
+### `shorthands.padding`
+
+```js
+import { makeStyles, shorthands } from '@griffel/react';
+
+const useClasses = makeStyles({
+  root: {
+    ...shorthands.padding('12px'),
+    ...shorthands.padding('12px', '24px'),
+    ...shorthands.padding('12px', '24px', '36px'),
+    ...shorthands.padding('12px', '24px', '36px', '48px'),
+  },
+});
+```
+
 ## `makeStaticStyles()`
 
 Creates styles attached to a global selector. Styles can be defined via objects:
diff --git a/yarn.lock b/yarn.lock
index 3ad9a33..fe095ce 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -11616,16 +11616,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"eslint-plugin-react-hooks@npm:4.2.0":
-  version: 4.2.0
-  resolution: "eslint-plugin-react-hooks@npm:4.2.0"
-  peerDependencies:
-    eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
-  checksum: ead5c5be3ded82a0cf295b064376adb1998a43e2262b605eecc0efc88283dec4e159ca39307fafb3d8e661dd08e5a4c8cdfed97eea78f923954f72bad6e20397
-  languageName: node
-  linkType: hard
-
-"eslint-plugin-react-hooks@npm:^4.2.0":
+"eslint-plugin-react-hooks@npm:4.3.0, eslint-plugin-react-hooks@npm:^4.2.0":
   version: 4.3.0
   resolution: "eslint-plugin-react-hooks@npm:4.3.0"
   peerDependencies:
@@ -13316,7 +13307,7 @@ __metadata:
     eslint-plugin-import: 2.25.2
     eslint-plugin-jsx-a11y: 6.5.1
     eslint-plugin-react: 7.27.0
-    eslint-plugin-react-hooks: 4.2.0
+    eslint-plugin-react-hooks: 4.3.0
     highlight.js: 11.4.0
     jest: 27.2.3
     js-beautify: ^1.14.0


Test patch:
diff --git a/packages/core/src/runtime/resolveStyleRules.test.ts b/packages/core/src/runtime/resolveStyleRules.test.ts
index 3ceaa78..403698b 100644
--- a/packages/core/src/runtime/resolveStyleRules.test.ts
+++ b/packages/core/src/runtime/resolveStyleRules.test.ts
@@ -327,6 +327,25 @@ describe('resolveStyleRules', () => {
       `);
     });
 
+    it('handles comma-separated selectors', () => {
+      expect(
+        resolveStyleRules({
+          ':active,:focus-within': {
+            paddingLeft: '10px',
+          },
+        }),
+      ).toMatchInlineSnapshot(`
+        .f14f5aie:active,
+        .f14f5aie:focus-within {
+          padding-left: 10px;
+        }
+        .f1sheuf0:active,
+        .f1sheuf0:focus-within {
+          padding-right: 10px;
+        }
+      `);
+    });
+
     it('handles media queries', () => {
       expect(
         resolveStyleRules({
diff --git a/packages/core/src/shorthands/outline.test.ts b/packages/core/src/shorthands/outline.test.ts
new file mode 100644
index 0000000..bcaaadf
--- /dev/null
+++ b/packages/core/src/shorthands/outline.test.ts
@@ -0,0 +1,31 @@
+import { outline } from './outline';
+describe('outline', () => {
+  it('for a given width', () => {
+    expect(outline('2px')).toEqual({
+      outlineWidth: '2px',
+    });
+  });
+
+  it('for a given width and style', () => {
+    expect(outline('2px', 'solid')).toEqual({
+      outlineWidth: '2px',
+      outlineStyle: 'solid',
+    });
+  });
+
+  it('for a given width, style and color', () => {
+    expect(outline('2px', 'solid', 'red')).toEqual({
+      outlineWidth: '2px',
+      outlineStyle: 'solid',
+      outlineColor: 'red',
+    });
+  });
+
+  it('for fallback value arrays', () => {
+    expect(outline(['2px', '4px'], ['solid', 'dashed'], ['red', 'green'])).toEqual({
+      outlineColor: ['red', 'green'],
+      outlineStyle: ['solid', 'dashed'],
+      outlineWidth: ['2px', '4px'],
+    });
+  });
+});