Repository: Khan/wonder-blocks
Pull request #2527: [WB-1776] Styles: Add wonder-blocks-styles package
Author: login: jandrade
Created: 2025-03-31T14:13:27Z | Merged: 2025-03-31T22:14:18Z

## Summary:

Created the `wonder-blocks-styles` package to include the focus styles for
Wonder Blocks (more styles will come later!).

This package will be used by other Wonder Blocks packages to ensure consistent
styles across the library. Also to provide a single source of truth for focus
styles in consumer applications.


Issue: https://khanacademy.atlassian.net/browse/WB-1776

## Test plan:

Navigate to `/?path=/docs/packages-styles-overview--docs` and verify that the
overview page looks fine.

Also navigate to `/?path=/docs/wonder-blocks-styles-styles--focus-styles` and
verify that the focus styles are applied correctly in both backgrounds (light
and dark).


Diff:
diff --git a/.changeset/honest-donkeys-happen.md b/.changeset/honest-donkeys-happen.md
new file mode 100644
index 0000000000..d810b35cb0
--- /dev/null
+++ b/.changeset/honest-donkeys-happen.md
@@ -0,0 +1,5 @@
+---
+"@khanacademy/wonder-blocks-styles": minor
+---
+
+Add `styles` package. Create `focusStyles` for focus indicators
diff --git a/__docs__/wonder-blocks-styles/__overview__.mdx b/__docs__/wonder-blocks-styles/__overview__.mdx
new file mode 100644
index 0000000000..44add35779
--- /dev/null
+++ b/__docs__/wonder-blocks-styles/__overview__.mdx
@@ -0,0 +1,32 @@
+import {Meta} from "@storybook/blocks";
+
+import Banner from "@khanacademy/wonder-blocks-banner";
+import {View} from "@khanacademy/wonder-blocks-core";
+import {spacing} from "@khanacademy/wonder-blocks-tokens";
+
+<Meta title="Packages / Styles / Overview" />
+
+# Wonder Blocks Styles
+
+Wonder Blocks Styles provides a set of styles that can be used to style your
+components. These styles are defined in the `@khanacademy/wonder-blocks-styles`
+package.
+
+These styles are meant to be used as global patterns for styling your
+components. They are not meant to be used as a replacement for our own Wonder
+Blocks components. Please use WB components whenever possible, then use these
+styles as a last resort to enforce consistency across experiences.
+
+<View style={{marginBlock: spacing.medium_16}}>
+    <Banner
+        kind="info"
+        layout="full-width"
+        text="We highly recommend using existing Wonder Blocks components instead of
+        creating your own components. Use these styles to augment existing components
+        when necessary."
+    />
+</View>
+
+### Styles
+
+- <a href="../?path=/docs/packages-styles-focus-styles--docs">Focus</a>
diff --git a/__docs__/wonder-blocks-styles/focus-styles.stories.tsx b/__docs__/wonder-blocks-styles/focus-styles.stories.tsx
new file mode 100644
index 0000000000..4e99e68407
--- /dev/null
+++ b/__docs__/wonder-blocks-styles/focus-styles.stories.tsx
@@ -0,0 +1,228 @@
+import * as React from "react";
+import {Meta, StoryObj} from "@storybook/react";
+import info from "@phosphor-icons/core/regular/info.svg";
+import ComponentInfo from "../components/component-info";
+import packageConfig from "../../packages/wonder-blocks-styles/package.json";
+import IconButton from "@khanacademy/wonder-blocks-icon-button";
+import {focusStyles} from "@khanacademy/wonder-blocks-styles";
+import {addStyle, View} from "@khanacademy/wonder-blocks-core";
+import {semanticColor, spacing} from "@khanacademy/wonder-blocks-tokens";
+import Clickable from "@khanacademy/wonder-blocks-clickable";
+import {ScenariosLayout} from "../components/scenarios-layout";
+
+/**
+ * Styles that can be used to create reusable focus states.
+ *
+ * ### Usage
+ *
+ * ```tsx
+ * import {focusStyles} from "@khanacademy/wonder-blocks-styles";
+ *
+ * <StyledButton style={focusStyles.focus}>
+ *      Custom button
+ * </StyledButton>
+ * ```
+ */
+export default {
+    title: "Packages / Styles / Focus Styles",
+    parameters: {
+        componentSubtitle: (
+            <ComponentInfo
+                name={packageConfig.name}
+                version={packageConfig.version}
+            />
+        ),
+    },
+} as Meta<any>;
+
+type Story = StoryObj<any>;
+
+const StyledButton = addStyle("button");
+
+/**
+ * A global focus style that can be applied to interactive elements.
+ *
+ * This style injects a combination of `outline` and `box-shadow` to indicate
+ * the element is focused. This is used for accessibility purposes as it allows
+ * the element to present a focus state on Windows High Contrast mode.
+ *
+ * In the example below, the focus style is applied to an `IconButton` component
+ * and to a `button` element.
+ */
+export const Focus: Story = {
+    name: "focus",
+    render: () => {
+        return (
+            <View
+                style={{
+                    padding: spacing.medium_16,
+                    flexDirection: "row",
+                    placeItems: "center",
+                }}
+            >
+                <View
+                    style={{
+                        background: semanticColor.status.success.background,
+                        padding: spacing.medium_16,
+                        gap: spacing.medium_16,
+                    }}
+                >
+                    <IconButton
+                        kind="tertiary"
+                        icon={info}
+                        style={focusStyles.focus}
+                    />
+                </View>
+                <View
+                    style={{
+                        background: semanticColor.surface.inverse,
+                        padding: spacing.medium_16,
+                        gap: spacing.medium_16,
+                    }}
+                >
+                    <IconButton
+                        kind="tertiary"
+                        icon={info}
+                        style={[
+                            focusStyles.focus,
+                            {color: semanticColor.text.inverse},
+                        ]}
+                    />
+                </View>
+            </View>
+        );
+    },
+    parameters: {
+        pseudo: {focusVisible: true},
+        chromatic: {
+            // Disabling because this is already covered by the Scenarios story.
+            disableSnapshot: true,
+        },
+    },
+};
+
+export const Scenarios: Story = {
+    render: () => {
+        const scenarios = [
+            {
+                name: "Using IconButton",
+                props: {
+                    children: (
+                        <IconButton
+                            kind="tertiary"
+                            icon={info}
+                            style={focusStyles.focus}
+                        />
+                    ),
+                },
+            },
+            {
+                name: "On a dark background",
+                props: {
+                    children: (
+                        <IconButton
+                            kind="tertiary"
+                            icon={info}
+                            style={[
+                                focusStyles.focus,
+                                {color: semanticColor.text.inverse},
+                            ]}
+                        />
+                    ),
+                    inverse: true,
+                },
+            },
+            {
+                name: "Using Clickable",
+                props: {
+                    children: (
+                        <Clickable onClick={() => {}} style={focusStyles.focus}>
+                            {() => "hello"}
+                        </Clickable>
+                    ),
+                },
+            },
+            {
+                name: "Using an HTML element",
+                props: {
+                    children: (
+                        <StyledButton style={focusStyles.focus}>
+                            Custom button
+                        </StyledButton>
+                    ),
+                },
+            },
+            {
+                name: "Spreading focus styles in an existing style",
+                props: {
+                    children: (
+                        <StyledButton
+                            style={{
+                                color: semanticColor.action.secondary
+                                    .progressive.default.foreground,
+                                // focus styles will be merged with the
+                                // defined styles
+                                ...focusStyles.focus,
+                            }}
+                        >
+                            Custom button
+                        </StyledButton>
+                    ),
+                },
+            },
+            {
+                name: "Overriding :focus-visible pseudo-class",
+                props: {
+                    children: (
+                        <StyledButton
+                            style={{
+                                color: semanticColor.action.secondary
+                                    .progressive.default.foreground,
+                                ":focus-visible": {
+                                    backgroundColor:
+                                        semanticColor.action.secondary
+                                            .progressive.default.background,
+                                    // focus styles will be merged with the
+                                    // component ones
+                                    ...focusStyles.focus[":focus-visible"],
+                                },
+                            }}
+                        >
+                            Custom button
+                        </StyledButton>
+                    ),
+                },
+            },
+        ];
+
+        return (
+            <ScenariosLayout scenarios={scenarios}>
+                {(props) => (
+                    <View
+                        {...props}
+                        style={{
+                            background: props.inverse
+                                ? semanticColor.surface.inverse
+                                : semanticColor.status.success.background,
+                            padding: spacing.medium_16,
+                            gap: spacing.medium_16,
+                        }}
+                    />
+                )}
+            </ScenariosLayout>
+        );
+    },
+    args: {},
+    parameters: {
+        pseudo: {focusVisible: true},
+        docs: {
+            canvas: {
+                sourceState: "shown",
+            },
+            source: {
+                type: "code",
+                excludeDecorators: true,
+            },
+        },
+    },
+};
diff --git a/packages/wonder-blocks-styles/package.json b/packages/wonder-blocks-styles/package.json
new file mode 100644
index 0000000000..68ce8816c6
--- /dev/null
+++ b/packages/wonder-blocks-styles/package.json
@@ -0,0 +1,24 @@
+{
+  "name": "@khanacademy/wonder-blocks-styles",
+  "version": "0.1.1",
+  "description": "Reusable Wonder Blocks styles.",
+  "main": "dist/index.js",
+  "module": "dist/es/index.js",
+  "types": "dist/index.d.ts",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1",
+    "prepublishOnly": "../../utils/publish/package-pre-publish-check.sh"
+  },
+  "author": "",
+  "license": "MIT",
+  "publishConfig": {
+    "access": "public"
+  },
+  "dependencies": {
+    "@babel/runtime": "catalog:",
+    "@khanacademy/wonder-blocks-tokens": "workspace:*"
+  },
+  "devDependencies": {
+    "@khanacademy/wb-dev-build-settings": "workspace:*"
+  }
+}
\ No newline at end of file
diff --git a/packages/wonder-blocks-styles/src/index.ts b/packages/wonder-blocks-styles/src/index.ts
new file mode 100644
index 0000000000..e3a7bdc4b8
--- /dev/null
+++ b/packages/wonder-blocks-styles/src/index.ts
@@ -0,0 +1,3 @@
+import * as focusStyles from "./styles/focus-styles";
+
+export {focusStyles};
diff --git a/packages/wonder-blocks-styles/src/styles/focus-styles.ts b/packages/wonder-blocks-styles/src/styles/focus-styles.ts
new file mode 100644
index 0000000000..d0bd84e033
--- /dev/null
+++ b/packages/wonder-blocks-styles/src/styles/focus-styles.ts
@@ -0,0 +1,16 @@
+import {border, semanticColor} from "@khanacademy/wonder-blocks-tokens";
+
+/**
+ * A global focus style that can be applied to interactive elements.
+ *
+ * This style injects a combination of `outline` and `box-shadow` to indicate
+ * the element is focused. This is used for accessibility purposes as it allows
+ * the element to present a focus state on Windows High Contrast mode.
+ */
+export const focus = {
+    ":focus-visible": {
+        boxShadow: `0 0 0 ${border.width.thin}px ${semanticColor.focus.inner}`,
+        outline: `${border.width.thin}px solid ${semanticColor.focus.outer}`,
+        outlineOffset: border.width.thin,
+    },
+};
diff --git a/packages/wonder-blocks-styles/tsconfig-build.json b/packages/wonder-blocks-styles/tsconfig-build.json
new file mode 100644
index 0000000000..e65b5c11a5
--- /dev/null
+++ b/packages/wonder-blocks-styles/tsconfig-build.json
@@ -0,0 +1,13 @@
+{
+    "exclude": [
+        "dist"
+    ],
+    "extends": "../tsconfig-shared.json",
+    "compilerOptions": {
+        "outDir": "./dist",
+        "rootDir": "src"
+    },
+    "references": [
+        {"path": "../wonder-blocks-tokens/tsconfig-build.json"},
+    ]
+}
\ No newline at end of file
diff --git a/packages/wonder-blocks-styles/types b/packages/wonder-blocks-styles/types
new file mode 120000
index 0000000000..8788aa2845
--- /dev/null
+++ b/packages/wonder-blocks-styles/types
@@ -0,0 +1 @@
+../../types
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8ab23d62d1..fc556dfa17 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1139,6 +1139,19 @@ importers:
         specifier: workspace:*
         version: link:../../build-settings
 
+  packages/wonder-blocks-styles:
+    dependencies:
+      '@babel/runtime':
+        specifier: 'catalog:'
+        version: 7.26.7
+      '@khanacademy/wonder-blocks-tokens':
+        specifier: workspace:*
+        version: link:../wonder-blocks-tokens
+    devDependencies:
+      '@khanacademy/wb-dev-build-settings':
+        specifier: workspace:*
+        version: link:../../build-settings
+
   packages/wonder-blocks-switch:
     dependencies:
       '@babel/runtime':
diff --git a/tsconfig-build.json b/tsconfig-build.json
index 9cb5f02904..c7d1d54108 100644
--- a/tsconfig-build.json
+++ b/tsconfig-build.json
@@ -28,6 +28,7 @@
         {"path": "./packages/wonder-blocks-popover/tsconfig-build.json"},
         {"path": "./packages/wonder-blocks-progress-spinner/tsconfig-build.json"},
         {"path": "./packages/wonder-blocks-search-field/tsconfig-build.json"},
+        {"path": "./packages/wonder-blocks-styles/tsconfig-build.json"},
         {"path": "./packages/wonder-blocks-switch/tsconfig-build.json"},
         {"path": "./packages/wonder-blocks-tabs/tsconfig-build.json"},
         {"path": "./packages/wonder-blocks-testing/tsconfig-build.json"},


Reviews:
user: login: beaesguerra
body: Yay we have a styles package 🎉  Thanks for setting this up!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-31T17:06:01Z
user: login: jandrade
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T17:28:24Z
user: login: marcysutton
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T21:19:56Z
user: login: jandrade
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T21:29:07Z
user: login: jandrade
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T21:30:45Z
user: login: jandrade
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T21:35:53Z
user: login: beaesguerra
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-31T21:49:58Z
user: login: beaesguerra
body: Thanks for adding more examples for documentation and tests! 
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-31T21:50:46Z

Review comments:
diff_hunk: @@ -0,0 +1,89 @@
+import * as React from "react";
+import {Meta, StoryObj} from "@storybook/react";
+import info from "@phosphor-icons/core/regular/info.svg";
+import ComponentInfo from "../components/component-info";
+import packageConfig from "../../packages/wonder-blocks-styles/package.json";
+import IconButton from "@khanacademy/wonder-blocks-icon-button";
+import {focusStyles} from "@khanacademy/wonder-blocks-styles";
+import {View} from "@khanacademy/wonder-blocks-core";
+import {semanticColor, spacing} from "@khanacademy/wonder-blocks-tokens";
+
+export default {
+    title: "Packages / Styles / Focus Styles",
+    parameters: {
+        componentSubtitle: (
+            <ComponentInfo
+                name={packageConfig.name}
+                version={packageConfig.version}
+            />
+        ),
+        docs: {
+            canvas: {
+                sourceState: "shown",
+            },
+        },
+    },
+} as Meta<any>;
+
+type StoryComponentType = StoryObj<any>;
+
+/**
+ * ### `focus`
+ * A global focus style that can be applied to any element.
+ *
+ * This style injects a combination of `outline` and `box-shadow` to indicate
+ * the element is focused. This is used for accessibility purposes as it allows
+ * the element to present a focus state on Windows High Contrast mode.
+ */
+export const Focus: StoryComponentType = {
+    name: "focus",
+    render: () => {
+        return (
+            <View
+                style={{
+                    padding: spacing.medium_16,
+                    flexDirection: "row",
+                    placeItems: "center",
+                }}
+            >
+                <View
+                    style={{
+                        background: semanticColor.status.success.background,
+                        padding: spacing.medium_16,
+                        gap: spacing.medium_16,
+                    }}
+                >
+                    <IconButton
+                        kind="tertiary"
+                        icon={info}
+                        style={focusStyles.focus}
path: __docs__/wonder-blocks-styles/focus-styles.stories.tsx
user: login: beaesguerra
body: Would it be helpful to use non-WB components as an example? Since the WB components have the WB focus styles built in already!
created_at: 2025-03-31T15:57:57Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,89 @@
+import * as React from "react";
+import {Meta, StoryObj} from "@storybook/react";
+import info from "@phosphor-icons/core/regular/info.svg";
+import ComponentInfo from "../components/component-info";
+import packageConfig from "../../packages/wonder-blocks-styles/package.json";
+import IconButton from "@khanacademy/wonder-blocks-icon-button";
+import {focusStyles} from "@khanacademy/wonder-blocks-styles";
+import {View} from "@khanacademy/wonder-blocks-core";
+import {semanticColor, spacing} from "@khanacademy/wonder-blocks-tokens";
+
+export default {
+    title: "Packages / Styles / Focus Styles",
+    parameters: {
+        componentSubtitle: (
+            <ComponentInfo
+                name={packageConfig.name}
+                version={packageConfig.version}
+            />
+        ),
+        docs: {
+            canvas: {
+                sourceState: "shown",
+            },
+        },
+    },
+} as Meta<any>;
+
+type StoryComponentType = StoryObj<any>;
+
+/**
+ * ### `focus`
+ * A global focus style that can be applied to any element.
+ *
+ * This style injects a combination of `outline` and `box-shadow` to indicate
+ * the element is focused. This is used for accessibility purposes as it allows
+ * the element to present a focus state on Windows High Contrast mode.
+ */
+export const Focus: StoryComponentType = {
+    name: "focus",
+    render: () => {
+        return (
+            <View
+                style={{
+                    padding: spacing.medium_16,
+                    flexDirection: "row",
+                    placeItems: "center",
+                }}
+            >
+                <View
+                    style={{
+                        background: semanticColor.status.success.background,
+                        padding: spacing.medium_16,
+                        gap: spacing.medium_16,
+                    }}
+                >
+                    <IconButton
+                        kind="tertiary"
+                        icon={info}
+                        style={focusStyles.focus}
+                    />
+                </View>
+                <View
+                    style={{
+                        background: semanticColor.surface.inverse,
+                        padding: spacing.medium_16,
+                        gap: spacing.medium_16,
+                    }}
+                >
+                    <IconButton
+                        kind="tertiary"
+                        icon={info}
+                        style={[
+                            focusStyles.focus,
+                            {color: semanticColor.text.inverse},
+                        ]}
+                    />
+                </View>
+            </View>
+        );
+    },
+    parameters: {
+        docs: {
+            source: {
+                code: JSON.stringify(focusStyles.focus, null, "\t"),
path: __docs__/wonder-blocks-styles/focus-styles.stories.tsx
user: login: beaesguerra
body: TIL that we can configure the code snippet to show by default, cool! 

Would it be helpful to use the code snippet to show how consumers can import and use the focus style? I was thinking since it's easy to copy the code snippet content, the actual styles with the hardcoded colors might get used instead of the exported `focusStyles` constant! 

If the goal was to show what the specific styles are, could we show in another way?

![image](https://github.com/user-attachments/assets/31b4fca2-70d3-4380-acd6-3c80aca0c5e4)

created_at: 2025-03-31T16:03:09Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,89 @@
+import * as React from "react";
+import {Meta, StoryObj} from "@storybook/react";
+import info from "@phosphor-icons/core/regular/info.svg";
+import ComponentInfo from "../components/component-info";
+import packageConfig from "../../packages/wonder-blocks-styles/package.json";
+import IconButton from "@khanacademy/wonder-blocks-icon-button";
+import {focusStyles} from "@khanacademy/wonder-blocks-styles";
+import {View} from "@khanacademy/wonder-blocks-core";
+import {semanticColor, spacing} from "@khanacademy/wonder-blocks-tokens";
+
+export default {
+    title: "Packages / Styles / Focus Styles",
+    parameters: {
+        componentSubtitle: (
+            <ComponentInfo
+                name={packageConfig.name}
+                version={packageConfig.version}
+            />
+        ),
+        docs: {
+            canvas: {
+                sourceState: "shown",
+            },
+        },
+    },
+} as Meta<any>;
+
+type StoryComponentType = StoryObj<any>;
+
+/**
+ * ### `focus`
+ * A global focus style that can be applied to any element.
path: __docs__/wonder-blocks-styles/focus-styles.stories.tsx
user: login: beaesguerra
body: suggestion: Do we want to specify that it should only be applied to interactive elements?
created_at: 2025-03-31T16:04:46Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,89 @@
+import * as React from "react";
+import {Meta, StoryObj} from "@storybook/react";
+import info from "@phosphor-icons/core/regular/info.svg";
+import ComponentInfo from "../components/component-info";
+import packageConfig from "../../packages/wonder-blocks-styles/package.json";
+import IconButton from "@khanacademy/wonder-blocks-icon-button";
+import {focusStyles} from "@khanacademy/wonder-blocks-styles";
+import {View} from "@khanacademy/wonder-blocks-core";
+import {semanticColor, spacing} from "@khanacademy/wonder-blocks-tokens";
+
+export default {
+    title: "Packages / Styles / Focus Styles",
+    parameters: {
+        componentSubtitle: (
+            <ComponentInfo
+                name={packageConfig.name}
+                version={packageConfig.version}
+            />
+        ),
+        docs: {
+            canvas: {
+                sourceState: "shown",
+            },
+        },
+    },
+} as Meta<any>;
+
+type StoryComponentType = StoryObj<any>;
+
+/**
+ * ### `focus`
+ * A global focus style that can be applied to any element.
+ *
+ * This style injects a combination of `outline` and `box-shadow` to indicate
+ * the element is focused. This is used for accessibility purposes as it allows
+ * the element to present a focus state on Windows High Contrast mode.
+ */
+export const Focus: StoryComponentType = {
+    name: "focus",
+    render: () => {
+        return (
+            <View
+                style={{
+                    padding: spacing.medium_16,
+                    flexDirection: "row",
+                    placeItems: "center",
+                }}
+            >
+                <View
+                    style={{
+                        background: semanticColor.status.success.background,
+                        padding: spacing.medium_16,
+                        gap: spacing.medium_16,
+                    }}
+                >
+                    <IconButton
+                        kind="tertiary"
+                        icon={info}
+                        style={focusStyles.focus}
path: __docs__/wonder-blocks-styles/focus-styles.stories.tsx
user: login: jandrade
body: Good point, let me get that in the Story and see how it works.
created_at: 2025-03-31T17:28:24Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,31 @@
+import {Meta} from "@storybook/blocks";
+
+import Banner from "@khanacademy/wonder-blocks-banner";
+import {View} from "@khanacademy/wonder-blocks-core";
+import {spacing} from "@khanacademy/wonder-blocks-tokens";
+
+<Meta title="Packages / Styles / Overview" />
+
+# Wonder Blocks Styles
+
+Wonder Blocks Styles provides a set of styles that can be used to style your
+components. These styles are defined in the `@khanacademy/wonder-blocks-styles`
+package.
+
+These styles are meant to be used as global patterns for styling your
+components. They are not meant to be used as a replacement for your own
+component styles.
path: __docs__/wonder-blocks-styles/__overview__.mdx
user: login: marcysutton
body: What does this mean? Should developers not use these styles directly?
created_at: 2025-03-31T21:19:56Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,89 @@
+import * as React from "react";
+import {Meta, StoryObj} from "@storybook/react";
+import info from "@phosphor-icons/core/regular/info.svg";
+import ComponentInfo from "../components/component-info";
+import packageConfig from "../../packages/wonder-blocks-styles/package.json";
+import IconButton from "@khanacademy/wonder-blocks-icon-button";
+import {focusStyles} from "@khanacademy/wonder-blocks-styles";
+import {View} from "@khanacademy/wonder-blocks-core";
+import {semanticColor, spacing} from "@khanacademy/wonder-blocks-tokens";
+
+export default {
+    title: "Packages / Styles / Focus Styles",
+    parameters: {
+        componentSubtitle: (
+            <ComponentInfo
+                name={packageConfig.name}
+                version={packageConfig.version}
+            />
+        ),
+        docs: {
+            canvas: {
+                sourceState: "shown",
+            },
+        },
+    },
+} as Meta<any>;
+
+type StoryComponentType = StoryObj<any>;
+
+/**
+ * ### `focus`
+ * A global focus style that can be applied to any element.
+ *
+ * This style injects a combination of `outline` and `box-shadow` to indicate
+ * the element is focused. This is used for accessibility purposes as it allows
+ * the element to present a focus state on Windows High Contrast mode.
+ */
+export const Focus: StoryComponentType = {
+    name: "focus",
+    render: () => {
+        return (
+            <View
+                style={{
+                    padding: spacing.medium_16,
+                    flexDirection: "row",
+                    placeItems: "center",
+                }}
+            >
+                <View
+                    style={{
+                        background: semanticColor.status.success.background,
+                        padding: spacing.medium_16,
+                        gap: spacing.medium_16,
+                    }}
+                >
+                    <IconButton
+                        kind="tertiary"
+                        icon={info}
+                        style={focusStyles.focus}
+                    />
+                </View>
+                <View
+                    style={{
+                        background: semanticColor.surface.inverse,
+                        padding: spacing.medium_16,
+                        gap: spacing.medium_16,
+                    }}
+                >
+                    <IconButton
+                        kind="tertiary"
+                        icon={info}
+                        style={[
+                            focusStyles.focus,
+                            {color: semanticColor.text.inverse},
+                        ]}
+                    />
+                </View>
+            </View>
+        );
+    },
+    parameters: {
+        docs: {
+            source: {
+                code: JSON.stringify(focusStyles.focus, null, "\t"),
path: __docs__/wonder-blocks-styles/focus-styles.stories.tsx
user: login: jandrade
body: Makes sense. I reverted this change to display the code associated to the story.
created_at: 2025-03-31T21:29:07Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,89 @@
+import * as React from "react";
+import {Meta, StoryObj} from "@storybook/react";
+import info from "@phosphor-icons/core/regular/info.svg";
+import ComponentInfo from "../components/component-info";
+import packageConfig from "../../packages/wonder-blocks-styles/package.json";
+import IconButton from "@khanacademy/wonder-blocks-icon-button";
+import {focusStyles} from "@khanacademy/wonder-blocks-styles";
+import {View} from "@khanacademy/wonder-blocks-core";
+import {semanticColor, spacing} from "@khanacademy/wonder-blocks-tokens";
+
+export default {
+    title: "Packages / Styles / Focus Styles",
+    parameters: {
+        componentSubtitle: (
+            <ComponentInfo
+                name={packageConfig.name}
+                version={packageConfig.version}
+            />
+        ),
+        docs: {
+            canvas: {
+                sourceState: "shown",
+            },
+        },
+    },
+} as Meta<any>;
+
+type StoryComponentType = StoryObj<any>;
+
+/**
+ * ### `focus`
+ * A global focus style that can be applied to any element.
+ *
+ * This style injects a combination of `outline` and `box-shadow` to indicate
+ * the element is focused. This is used for accessibility purposes as it allows
+ * the element to present a focus state on Windows High Contrast mode.
+ */
+export const Focus: StoryComponentType = {
+    name: "focus",
+    render: () => {
+        return (
+            <View
+                style={{
+                    padding: spacing.medium_16,
+                    flexDirection: "row",
+                    placeItems: "center",
+                }}
+            >
+                <View
+                    style={{
+                        background: semanticColor.status.success.background,
+                        padding: spacing.medium_16,
+                        gap: spacing.medium_16,
+                    }}
+                >
+                    <IconButton
+                        kind="tertiary"
+                        icon={info}
+                        style={focusStyles.focus}
path: __docs__/wonder-blocks-styles/focus-styles.stories.tsx
user: login: jandrade
body: @beaesguerra I reworked this and kept the `IconButton` example here. FWIW One of the main reasons for adding this IconButton example is b/c this is going to be used as a replacement for the `light` variant. I added a separate `Scenarios` story to cover this style in different contexts. Let me know what you think, happy to make more changes if you think there are better ways to document and test this :).
created_at: 2025-03-31T21:30:45Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,31 @@
+import {Meta} from "@storybook/blocks";
+
+import Banner from "@khanacademy/wonder-blocks-banner";
+import {View} from "@khanacademy/wonder-blocks-core";
+import {spacing} from "@khanacademy/wonder-blocks-tokens";
+
+<Meta title="Packages / Styles / Overview" />
+
+# Wonder Blocks Styles
+
+Wonder Blocks Styles provides a set of styles that can be used to style your
+components. These styles are defined in the `@khanacademy/wonder-blocks-styles`
+package.
+
+These styles are meant to be used as global patterns for styling your
+components. They are not meant to be used as a replacement for your own
+component styles.
path: __docs__/wonder-blocks-styles/__overview__.mdx
user: login: jandrade
body: hmm good question, I'll reword it to state that we still suggest using WB components as the default solution (instead of "your own component styles"). Do you think that would make more sense? or there would be missing more clarification?
created_at: 2025-03-31T21:35:53Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,31 @@
+import {Meta} from "@storybook/blocks";
+
+import Banner from "@khanacademy/wonder-blocks-banner";
+import {View} from "@khanacademy/wonder-blocks-core";
+import {spacing} from "@khanacademy/wonder-blocks-tokens";
+
+<Meta title="Packages / Styles / Overview" />
+
+# Wonder Blocks Styles
+
+Wonder Blocks Styles provides a set of styles that can be used to style your
+components. These styles are defined in the `@khanacademy/wonder-blocks-styles`
+package.
+
+These styles are meant to be used as global patterns for styling your
+components. They are not meant to be used as a replacement for your own
+component styles.
path: __docs__/wonder-blocks-styles/__overview__.mdx
user: login: beaesguerra
body: My understanding is that dev teams should prioritize using WB components and then use these global styles (and tokens!) when creating custom components that aren't in the design system! 
created_at: 2025-03-31T21:49:58Z
author_association: MEMBER

PR comments:
user: login: changeset-bot[bot]
created_at: 2025-03-31T14:13:31Z
body: ###  🦋  Changeset detected

Latest commit: 1fcc86f36c752750dd0df49581df17610246faac

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 1 package</summary>

  | Name                              | Type  |
| --------------------------------- | ----- |
| @khanacademy/wonder-blocks-styles | Minor |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/Khan/wonder-blocks/new/wb-styles-focus-v2?filename=.changeset/gold-ties-smile.md&value=---%0A%22%40khanacademy%2Fwonder-blocks-styles%22%3A%20patch%0A---%0A%0A%5BWB-1776%5D%20Styles%3A%20Add%20wonder-blocks-styles%20package%0A)


author_association: NONE
user: login: khan-actions-bot
created_at: 2025-03-31T14:13:43Z
body: # Gerald

<details>
<summary><b>Required Reviewers</b></summary>

* `@Khan/wonder-blocks` for changes to `pnpm-lock.yaml`, `tsconfig-build.json`, `.changeset/honest-donkeys-happen.md`, `__docs__/wonder-blocks-styles/__overview__.mdx`, `__docs__/wonder-blocks-styles/focus-styles.stories.tsx`, `packages/wonder-blocks-styles/package.json`, `packages/wonder-blocks-styles/tsconfig-build.json`, `packages/wonder-blocks-styles/src/index.ts`, `packages/wonder-blocks-styles/src/styles/focus-styles.ts`
</details>


__________________________________________________________________________________________________________________________________
_Don't want to be involved in this pull request? Comment `#removeme` and we won't notify you of further changes._
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-31T14:16:29Z
body: ## npm Snapshot: Published
🎉 Good news!! We've packaged up the latest commit from this PR (5b0040c9) and published all packages with changesets to npm.

You can install the packages in webapp by running:
```sh
./services/static/dev/tools/deploy_wonder_blocks.js --tag="PR2527"
```

Packages can also be installed manually by running:
```sh
pnpm add @khanacademy/wonder-blocks-<package-name>@PR2527
```
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-31T14:16:33Z
body: **Size Change:** +270 B (+0.26%) 

**Total Size:** 103 kB

| Filename | Size | Change |  |
| :--- | :---: | :---: | :---: |
| `packages/wonder-blocks-styles/dist/es/index.js` | 270 B | +270 B (new file) | 🆕 |

<details><summary>ℹ️ <strong>View Unchanged</strong></summary>

| Filename | Size |
| :--- | :---: |
| `packages/wonder-blocks-accordion/dist/es/index.js` | 3.54 kB |
| `packages/wonder-blocks-announcer/dist/es/index.js` | 2.04 kB |
| `packages/wonder-blocks-banner/dist/es/index.js` | 1.55 kB |
| `packages/wonder-blocks-birthday-picker/dist/es/index.js` | 1.88 kB |
| `packages/wonder-blocks-breadcrumbs/dist/es/index.js` | 886 B |
| `packages/wonder-blocks-button/dist/es/index.js` | 4.34 kB |
| `packages/wonder-blocks-cell/dist/es/index.js` | 2.35 kB |
| `packages/wonder-blocks-clickable/dist/es/index.js` | 3.07 kB |
| `packages/wonder-blocks-core/dist/es/index.js` | 2.85 kB |
| `packages/wonder-blocks-data/dist/es/index.js` | 6.25 kB |
| `packages/wonder-blocks-dropdown/dist/es/index.js` | 19.8 kB |
| `packages/wonder-blocks-form/dist/es/index.js` | 6.04 kB |
| `packages/wonder-blocks-grid/dist/es/index.js` | 1.36 kB |
| `packages/wonder-blocks-icon-button/dist/es/index.js` | 3.13 kB |
| `packages/wonder-blocks-icon/dist/es/index.js` | 873 B |
| `packages/wonder-blocks-labeled-field/dist/es/index.js` | 1.26 kB |
| `packages/wonder-blocks-layout/dist/es/index.js` | 1.82 kB |
| `packages/wonder-blocks-link/dist/es/index.js` | 2.04 kB |
| `packages/wonder-blocks-modal/dist/es/index.js` | 5.5 kB |
| `packages/wonder-blocks-pill/dist/es/index.js` | 1.49 kB |
| `packages/wonder-blocks-popover/dist/es/index.js` | 4.83 kB |
| `packages/wonder-blocks-progress-spinner/dist/es/index.js` | 1.52 kB |
| `packages/wonder-blocks-search-field/dist/es/index.js` | 1.32 kB |
| `packages/wonder-blocks-switch/dist/es/index.js` | 2.02 kB |
| `packages/wonder-blocks-tabs/dist/es/index.js` | 1.25 kB |
| `packages/wonder-blocks-testing-core/dist/es/index.js` | 3.91 kB |
| `packages/wonder-blocks-testing/dist/es/index.js` | 1.07 kB |
| `packages/wonder-blocks-theming/dist/es/index.js` | 679 B |
| `packages/wonder-blocks-timing/dist/es/index.js` | 1.79 kB |
| `packages/wonder-blocks-tokens/dist/es/index.js` | 2.7 kB |
| `packages/wonder-blocks-toolbar/dist/es/index.js` | 923 B |
| `packages/wonder-blocks-tooltip/dist/es/index.js` | 7.01 kB |
| `packages/wonder-blocks-typography/dist/es/index.js` | 1.23 kB |

</details>



<a href="https://github.com/preactjs/compressed-size-action"><sub>compressed-size-action</sub></a>
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-31T14:19:16Z
body: # A new build was pushed to Chromatic! :rocket:
https://5e1bf4b385e3fb0020b7073c-bgkfccmciz.chromatic.com/

## Chromatic results:

| Metric | Total |
|---|---|
| Captured snapshots | 0 |
| Tests with visual changes | 0 |
| Total stories | 600 |
| Inherited (not captured) snapshots [TurboSnap] | 405 |
| Tests on the build | 405 |
author_association: CONTRIBUTOR
user: login: codecov[bot]
created_at: 2025-03-31T22:17:23Z
body: ## [Codecov](https://app.codecov.io/gh/Khan/wonder-blocks/pull/2527?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Khan) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 0.00%. Comparing base [(`4fc99e0`)](https://app.codecov.io/gh/Khan/wonder-blocks/commit/4fc99e04ed85d70bf2da820f79d848c91ffeca68?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Khan) to head [(`1fcc86f`)](https://app.codecov.io/gh/Khan/wonder-blocks/commit/1fcc86f36c752750dd0df49581df17610246faac?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Khan).
> Report is 1 commits behind head on feature/icon-button-cleanup.

<details><summary>Additional details and impacted files</summary>


[![Impacted file tree graph](https://app.codecov.io/gh/Khan/wonder-blocks/pull/2527/graphs/tree.svg?width=650&height=150&src=pr&token=ny8PAxdjV4&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Khan)](https://app.codecov.io/gh/Khan/wonder-blocks/pull/2527?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Khan)

```diff
@@                 Coverage Diff                 @@
##   feature/icon-button-cleanup   #2527   +/-   ##
===================================================
===================================================

```

------

[Continue to review full report in Codecov by Sentry](https://app.codecov.io/gh/Khan/wonder-blocks/pull/2527?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Khan).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Khan)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://app.codecov.io/gh/Khan/wonder-blocks/pull/2527?dropdown=coverage&src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Khan). Last update [4fc99e0...1fcc86f](https://app.codecov.io/gh/Khan/wonder-blocks/pull/2527?dropdown=coverage&src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Khan). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Khan).

</details>
<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE