Repository: microsoft/griffel
Pull request #108 (2022-04-26 14:24:01)

feat(Devtools): enable it via content_scripts
When griffel devtools start, content-script.js will run and add `__GRIFFEL_DEVTOOLS__` as `enabled` in sessionStorage.
Griffel core can use it to determine if devtools should be injected.

Reason for `sessionStorage`: content script runs in an isolated world. Changes like `document.XXX = yyy` in content script will not be reflected on the page (page will still have `document.XXX = undefined`)


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/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/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/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:


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'],
+    });
+  });
+});