Repository: Khan/perseus
Pull request #2486: Update Free Response styles and accessibility
Author: login: aag
Labels: name: olc-5.0.f1826
Created: 2025-05-14T19:56:16Z | Merged: 2025-05-15T14:52:31Z

## Summary:
This commit updates the styles and layout of the Free Response Widget to
match the designs. It also updates some markup to improve the
accessibility of the widget, including using the `LabeledField` Wonder
Blocks component.

The designs can be found in Figma:
https://www.figma.com/design/h5nZthMLTd56M0GLYsrDry/ELA---Free-Response?node-id=10184-1509&t=7etutvzW6ikoM9cS-1

Issue: https://khanacademy.atlassian.net/browse/AX-963

## Test plan:
- View the widget variants in Storybook.
- Verify that the rendered widgets match the designs.

![image](https://github.com/user-attachments/assets/9b6ea08f-667d-4e4d-802f-d5256e520354)



Diff:
diff --git a/.changeset/spicy-seas-mix.md b/.changeset/spicy-seas-mix.md
new file mode 100644
index 00000000000..a1907072809
--- /dev/null
+++ b/.changeset/spicy-seas-mix.md
@@ -0,0 +1,7 @@
+---
+"@khanacademy/perseus": patch
+"@khanacademy/perseus-core": patch
+"@khanacademy/perseus-editor": patch
+---
+
+Update visual styling and a11y features
diff --git a/packages/perseus/package.json b/packages/perseus/package.json
index d825ff23011..d9f4a5321de 100644
--- a/packages/perseus/package.json
+++ b/packages/perseus/package.json
@@ -65,6 +65,7 @@
         "@khanacademy/wonder-blocks-form": "catalog:",
         "@khanacademy/wonder-blocks-icon-button": "catalog:",
         "@khanacademy/wonder-blocks-icon": "catalog:",
+        "@khanacademy/wonder-blocks-labeled-field": "catalog:",
         "@khanacademy/wonder-blocks-layout": "catalog:",
         "@khanacademy/wonder-blocks-link": "catalog:",
         "@khanacademy/wonder-blocks-pill": "catalog:",
@@ -101,6 +102,7 @@
         "@khanacademy/wonder-blocks-form": "catalog:",
         "@khanacademy/wonder-blocks-icon": "catalog:",
         "@khanacademy/wonder-blocks-icon-button": "catalog:",
+        "@khanacademy/wonder-blocks-labeled-field": "catalog:",
         "@khanacademy/wonder-blocks-layout": "catalog:",
         "@khanacademy/wonder-blocks-link": "catalog:",
         "@khanacademy/wonder-blocks-pill": "catalog:",
diff --git a/packages/perseus/src/styles/perseus-renderer.less b/packages/perseus/src/styles/perseus-renderer.less
index 41bca00c03f..de2f76e8ef5 100644
--- a/packages/perseus/src/styles/perseus-renderer.less
+++ b/packages/perseus/src/styles/perseus-renderer.less
@@ -419,6 +419,7 @@ div.graphie {
 @import "./widgets/categorizer.less";
 @import "./widgets/dropdown.less";
 @import "./widgets/expression.less";
+@import "./widgets/free-response.css";
 @import "./widgets/grapher.less";
 @import "./widgets/graded-group.less";
 @import "./widgets/group.less";
diff --git a/packages/perseus/src/styles/widgets/free-response.css b/packages/perseus/src/styles/widgets/free-response.css
new file mode 100644
index 00000000000..6533e43dba1
--- /dev/null
+++ b/packages/perseus/src/styles/widgets/free-response.css
@@ -0,0 +1,20 @@
+.framework-perseus {
+    .free-response .free-response-question {
+        /* Default to bold text in the question. */
+        .paragraph {
+            font-weight: 700;
+        }
+
+        /* Remove the top margin from the first paragraph, so there isn't forced
+           spacing above the question. */
+        :nth-child(1 of .paragraph) {
+            margin-top: 0;
+        }
+
+        /* Remove the bottom margin from the last paragraph, so there isn't a
+           large space between the question and the textarea. */
+        :nth-last-child(1 of .paragraph) {
+            margin-bottom: 0;
+        }
+    }
+}
diff --git a/packages/perseus/src/widgets/free-response/__snapshots__/free-response.test.tsx.snap b/packages/perseus/src/widgets/free-response/__snapshots__/free-response.test.tsx.snap
index 83be6cf6e05..9050205f81b 100644
--- a/packages/perseus/src/widgets/free-response/__snapshots__/free-response.test.tsx.snap
+++ b/packages/perseus/src/widgets/free-response/__snapshots__/free-response.test.tsx.snap
@@ -16,46 +16,60 @@ exports[`free-response widget should snapshot on mobile: first mobile render 1`]
           class="perseus-widget-container widget-nohighlight widget-block"
         >
           <div
-            class="default_xu2jcg-o_O-container_lci7r"
+            class="default_xu2jcg-o_O-container_lci7r free-response"
           >
-            <label
-              class="labelAndTextarea_b4ep1"
+            <div
+              class="default_xu2jcg"
             >
-              <span
-                class="text_f1191h-o_O-LabelLarge_5s82ln"
+              <label
+                class="text_f1191h-o_O-LabelMedium_1voxh4u-o_O-textWordBreak_6z3til-o_O-label_wdxvi6-o_O-labelWithNoDescription_p67whi-o_O-questionLabel_u5j6yl"
+                for=":r2:-labeled-field-field"
+                id=":r2:-labeled-field-label"
               >
                 <div
-                  class="perseus-renderer perseus-renderer-responsive"
+                  class="default_xu2jcg free-response-question"
                 >
                   <div
-                    class="paragraph"
-                    data-perseus-paragraph-index="0"
+                    class="perseus-renderer perseus-renderer-responsive"
                   >
                     <div
                       class="paragraph"
+                      data-perseus-paragraph-index="0"
                     >
-                      test-question
+                      <div
+                        class="paragraph"
+                      >
+                        test-question
+                      </div>
                     </div>
                   </div>
                 </div>
-              </span>
+              </label>
               <div
                 class="default_xu2jcg-o_O-inlineStyles_zdxht7"
               >
                 <textarea
+                  aria-describedby=""
                   aria-invalid="false"
                   aria-required="false"
                   class="textarea_nfcfnx-o_O-LabelMedium_1rew30o-o_O-default_1elaks2-o_O-defaultFocus_drkasj-o_O-textarea_1x0fg6n"
-                  id=":r1:"
+                  id=":r2:-labeled-field-field"
                   placeholder="test-placeholder"
                 />
               </div>
-            </label>
+              <div
+                aria-atomic="true"
+                aria-live="assertive"
+                class="default_xu2jcg-o_O-errorSection_375epe"
+                id=":r2:-labeled-field-error"
+              />
+            </div>
             <div
               class="default_xu2jcg"
             >
               <span
-                class="text_f1191h"
+                class="text_f1191h-o_O-characterCountText_12vvpv1"
+                role="status"
               >
                 0 / 500 Characters
               </span>
@@ -84,46 +98,60 @@ exports[`free-response widget should snapshot: first render 1`] = `
           class="perseus-widget-container widget-nohighlight widget-block"
         >
           <div
-            class="default_xu2jcg-o_O-container_lci7r"
+            class="default_xu2jcg-o_O-container_lci7r free-response"
           >
-            <label
-              class="labelAndTextarea_b4ep1"
+            <div
+              class="default_xu2jcg"
             >
-              <span
-                class="text_f1191h-o_O-LabelLarge_5s82ln"
+              <label
+                class="text_f1191h-o_O-LabelMedium_1voxh4u-o_O-textWordBreak_6z3til-o_O-label_wdxvi6-o_O-labelWithNoDescription_p67whi-o_O-questionLabel_u5j6yl"
+                for=":r0:-labeled-field-field"
+                id=":r0:-labeled-field-label"
               >
                 <div
-                  class="perseus-renderer perseus-renderer-responsive"
+                  class="default_xu2jcg free-response-question"
                 >
                   <div
-                    class="paragraph"
-                    data-perseus-paragraph-index="0"
+                    class="perseus-renderer perseus-renderer-responsive"
                   >
                     <div
                       class="paragraph"
+                      data-perseus-paragraph-index="0"
                     >
-                      test-question
+                      <div
+                        class="paragraph"
+                      >
+                        test-question
+                      </div>
                     </div>
                   </div>
                 </div>
-              </span>
+              </label>
               <div
                 class="default_xu2jcg-o_O-inlineStyles_zdxht7"
               >
                 <textarea
+                  aria-describedby=""
                   aria-invalid="false"
                   aria-required="false"
                   class="textarea_nfcfnx-o_O-LabelMedium_1rew30o-o_O-default_1elaks2-o_O-defaultFocus_drkasj-o_O-textarea_1x0fg6n"
-                  id=":r0:"
+                  id=":r0:-labeled-field-field"
                   placeholder="test-placeholder"
                 />
               </div>
-            </label>
+              <div
+                aria-atomic="true"
+                aria-live="assertive"
+                class="default_xu2jcg-o_O-errorSection_375epe"
+                id=":r0:-labeled-field-error"
+              />
+            </div>
             <div
               class="default_xu2jcg"
             >
               <span
-                class="text_f1191h"
+                class="text_f1191h-o_O-characterCountText_12vvpv1"
+                role="status"
               >
                 0 / 500 Characters
               </span>
diff --git a/packages/perseus/src/widgets/free-response/free-response.stories.tsx b/packages/perseus/src/widgets/free-response/free-response.stories.tsx
index 82ead000a8a..c6017a19591 100644
--- a/packages/perseus/src/widgets/free-response/free-response.stories.tsx
+++ b/packages/perseus/src/widgets/free-response/free-response.stories.tsx
@@ -33,12 +33,13 @@ export const UnlimitedCharacters: Story = {
         allowUnlimitedCharacters: true,
         characterLimit: 500,
         placeholder: "Enter your answer here",
-        question: "What is the theme of the essay?",
+        question:
+            "What is the theme of the essay?\n\nPut your answer in your own words.",
     },
 };
 
 export const QuestionWithTex: Story = {
-    storyName: "Question with TeX content",
+    name: "Question with TeX content",
     args: {
         allowUnlimitedCharacters: true,
         characterLimit: 500,
diff --git a/packages/perseus/src/widgets/free-response/free-response.tsx b/packages/perseus/src/widgets/free-response/free-response.tsx
index 99ed874fb80..015adf8868d 100644
--- a/packages/perseus/src/widgets/free-response/free-response.tsx
+++ b/packages/perseus/src/widgets/free-response/free-response.tsx
@@ -8,10 +8,15 @@
 import {Text, View} from "@khanacademy/wonder-blocks-core";
 import {TextArea} from "@khanacademy/wonder-blocks-form";
 import {PhosphorIcon} from "@khanacademy/wonder-blocks-icon";
-import {color, spacing} from "@khanacademy/wonder-blocks-tokens";
-import {LabelLarge} from "@khanacademy/wonder-blocks-typography";
+import {LabeledField} from "@khanacademy/wonder-blocks-labeled-field";
+import {
+    color,
+    font,
+    spacing,
+    semanticColor,
+} from "@khanacademy/wonder-blocks-tokens";
 import warningCircleIcon from "@phosphor-icons/core/regular/warning-circle.svg";
-import {StyleSheet, css} from "aphrodite";
+import {StyleSheet} from "aphrodite";
 import * as React from "react";
 
 import {PerseusI18nContext} from "../../components/i18n-context";
@@ -81,11 +86,13 @@ export class FreeResponse
         return (
             <View>
                 <Text
-                    style={
+                    role="status"
+                    style={[
+                        styles.characterCountText,
                         this.isOverLimit()
                             ? styles.overCharacterLimit
-                            : undefined
-                    }
+                            : undefined,
+                    ]}
                 >
                     {this.isOverLimit() && (
                         <PhosphorIcon
@@ -103,22 +110,29 @@ export class FreeResponse
 
     render(): React.ReactNode {
         return (
-            <View style={styles.container}>
-                <label className={css(styles.labelAndTextarea)}>
-                    <LabelLarge>
-                        <Renderer
-                            content={this.props.question}
-                            strings={this.context.strings}
+            <View style={styles.container} className={"free-response"}>
+                <LabeledField
+                    label={
+                        <View className="free-response-question">
+                            <Renderer
+                                content={this.props.question}
+                                strings={this.context.strings}
+                            />
+                        </View>
+                    }
+                    field={
+                        <TextArea
+                            error={this.isOverLimit()}
+                            onChange={this.handleChange}
+                            placeholder={this.props.placeholder}
+                            style={styles.textarea}
+                            value={this.state.currentValue}
                         />
-                    </LabelLarge>
-                    <TextArea
-                        error={this.isOverLimit()}
-                        onChange={this.handleChange}
-                        placeholder={this.props.placeholder}
-                        style={styles.textarea}
-                        value={this.state.currentValue}
-                    />
-                </label>
+                    }
+                    styles={{
+                        label: styles.questionLabel,
+                    }}
+                />
                 {this.renderCharacterCount()}
             </View>
         );
@@ -138,14 +152,16 @@ const styles = StyleSheet.create({
     container: {
         gap: spacing.xSmall_8,
     },
-    labelAndTextarea: {
-        display: "flex",
-        flexDirection: "column",
-        gap: spacing.small_12,
+    characterCountText: {
+        color: semanticColor.text.secondary,
+        fontSize: font.size.small,
     },
     overCharacterLimit: {
         color: color.red,
     },
+    questionLabel: {
+        fontWeight: font.weight.bold,
+    },
     textarea: {
         padding: spacing.medium_16,
     },
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7ef9a1d19e4..3984922474f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -39,6 +39,9 @@ catalogs:
     '@khanacademy/wonder-blocks-icon-button':
       specifier: 9.0.4
       version: 9.0.4
+    '@khanacademy/wonder-blocks-labeled-field':
+      specifier: 3.0.6
+      version: 3.0.6
     '@khanacademy/wonder-blocks-layout':
       specifier: 3.1.9
       version: 3.1.9
@@ -718,6 +721,9 @@ importers:
       '@khanacademy/wonder-blocks-icon-button':
         specifier: 'catalog:'
         version: 9.0.4(@phosphor-icons/core@2.0.2)(aphrodite@1.2.5)(react-dom@18.3.1(react@18.3.1))(react-router-dom-v5-compat@6.30.0(react-dom@18.3.1(react@18.3.1))(react-router-dom@5.3.4(react@18.3.1))(react@18.3.1))(react-router-dom@5.3.4(react@18.3.1))(react-router@6.30.0(react@18.3.1))(react@18.3.1)
+      '@khanacademy/wonder-blocks-labeled-field':
+        specifier: 'catalog:'
+        version: 3.0.6(@phosphor-icons/core@2.0.2)(aphrodite@1.2.5)(react-dom@18.3.1(react@18.3.1))(react-router-dom@5.3.4(react@18.3.1))(react-router@6.30.0(react@18.3.1))(react@18.3.1)
       '@khanacademy/wonder-blocks-layout':
         specifier: 'catalog:'
         version: 3.1.9(aphrodite@1.2.5)(react-dom@18.3.1(react@18.3.1))(react-router-dom@5.3.4(react@18.3.1))(react-router@6.30.0(react@18.3.1))(react@18.3.1)
@@ -2380,6 +2386,19 @@ packages:
       aphrodite: ^1.2.5
       react: 18.2.0
 
+  '@khanacademy/wonder-blocks-labeled-field@3.0.6':
+    resolution: {integrity: sha512-HIwGBEu+Hz0zqKm0QmAzCAoz/auJhCGgrpDyut/H7s5gZPxf7TLYDa6D9SHZ00CqnJJYtL8ARhxFAYBmeqqzvA==}
+    peerDependencies:
+      '@phosphor-icons/core': ^2.0.2
+      aphrodite: ^1.2.5
+      react: 18.2.0
+
+  '@khanacademy/wonder-blocks-layout@3.1.10':
+    resolution: {integrity: sha512-2meShF4mAlPVTXiYLhD0qxPyruRn+s2mdQzQt+CWJzD2H3u6EJPzb1xHv3R1T1njmfik1pfBMwGedc8COmgcZQ==}
+    peerDependencies:
+      aphrodite: ^1.2.5
+      react: 18.2.0
+
   '@khanacademy/wonder-blocks-layout@3.1.9':
     resolution: {integrity: sha512-iF8HWb789RjiEIh8zLH5ZGnsHKomEXB3M57vhEc8SNxFNffjpyiz8238Po79/6XK+QxniAb9LNG1SPnlz9eZLQ==}
     peerDependencies:
@@ -2454,6 +2473,9 @@ packages:
     peerDependencies:
       react: 18.2.0
 
+  '@khanacademy/wonder-blocks-tokens@10.0.0':
+    resolution: {integrity: sha512-E7WQreN7tG6UT5vyTAdXHkEMQSMFkwQO+64LO/+J+SXqMzQnflGzwjRWy0OG3bUzNy2lKDramW3QrP88DxuFgA==}
+
   '@khanacademy/wonder-blocks-tokens@9.0.0':
     resolution: {integrity: sha512-l8MweiUGIktyTAW7lTZ1vF01bLOSEpQzPXIWqZ/XmflPz57K6sHZnvMowZqusP1t3LrEHnMjuu9izCjXGslzWA==}
 
@@ -2478,6 +2500,12 @@ packages:
       aphrodite: ^1.2.5
       react: 18.2.0
 
+  '@khanacademy/wonder-blocks-typography@3.2.0':
+    resolution: {integrity: sha512-cg5cqMD3dRldpI9yZwaPnEk02RREU/u9tdhfgsoi7PZEMqV0IDGH9XtFzZ8RqLR1XgCXMK01qqmPVz3hoR7K6Q==}
+    peerDependencies:
+      aphrodite: ^1.2.5
+      react: 18.2.0
+
   '@khanacademy/wonder-stuff-core@1.5.4':
     resolution: {integrity: sha512-Z1DOHdSNrXyIKCgYsq6XKT6ZTRq5bb7Daof8mN31O30twStrwBynP5fS0lARS9qgxycaueThEcmeCF8AXyU21g==}
     engines: {node: '>=16'}
@@ -10960,6 +10988,31 @@ snapshots:
       - react-router
       - react-router-dom
 
+  '@khanacademy/wonder-blocks-labeled-field@3.0.6(@phosphor-icons/core@2.0.2)(aphrodite@1.2.5)(react-dom@18.3.1(react@18.3.1))(react-router-dom@5.3.4(react@18.3.1))(react-router@6.30.0(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@khanacademy/wonder-blocks-core': 12.2.1(aphrodite@1.2.5)(react-dom@18.3.1(react@18.3.1))(react-router-dom@5.3.4(react@18.3.1))(react-router@6.30.0(react@18.3.1))(react@18.3.1)
+      '@khanacademy/wonder-blocks-layout': 3.1.10(aphrodite@1.2.5)(react-dom@18.3.1(react@18.3.1))(react-router-dom@5.3.4(react@18.3.1))(react-router@6.30.0(react@18.3.1))(react@18.3.1)
+      '@khanacademy/wonder-blocks-tokens': 10.0.0(aphrodite@1.2.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@khanacademy/wonder-blocks-typography': 3.2.0(aphrodite@1.2.5)(react-dom@18.3.1(react@18.3.1))(react-router-dom@5.3.4(react@18.3.1))(react-router@6.30.0(react@18.3.1))(react@18.3.1)
+      '@phosphor-icons/core': 2.0.2
+      aphrodite: 1.2.5
+      react: 18.3.1
+    transitivePeerDependencies:
+      - react-dom
+      - react-router
+      - react-router-dom
+
+  '@khanacademy/wonder-blocks-layout@3.1.10(aphrodite@1.2.5)(react-dom@18.3.1(react@18.3.1))(react-router-dom@5.3.4(react@18.3.1))(react-router@6.30.0(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@khanacademy/wonder-blocks-core': 12.2.1(aphrodite@1.2.5)(react-dom@18.3.1(react@18.3.1))(react-router-dom@5.3.4(react@18.3.1))(react-router@6.30.0(react@18.3.1))(react@18.3.1)
+      '@khanacademy/wonder-blocks-tokens': 10.0.0(aphrodite@1.2.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      aphrodite: 1.2.5
+      react: 18.3.1
+    transitivePeerDependencies:
+      - react-dom
+      - react-router
+      - react-router-dom
+
   '@khanacademy/wonder-blocks-layout@3.1.9(aphrodite@1.2.5)(react-dom@18.3.1(react@18.3.1))(react-router-dom@5.3.4(react@18.3.1))(react-router@5.3.4(react@18.3.1))(react@18.3.1)':
     dependencies:
       '@babel/runtime': 7.27.1
@@ -11152,6 +11205,14 @@ snapshots:
     dependencies:
       react: 18.3.1
 
+  '@khanacademy/wonder-blocks-tokens@10.0.0(aphrodite@1.2.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@khanacademy/wonder-blocks-theming': 3.3.0(aphrodite@1.2.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+    transitivePeerDependencies:
+      - aphrodite
+      - react
+      - react-dom
+
   '@khanacademy/wonder-blocks-tokens@9.0.0(aphrodite@1.2.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
     dependencies:
       '@khanacademy/wonder-blocks-theming': 3.3.0(aphrodite@1.2.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -11214,6 +11275,17 @@ snapshots:
       - react-router
       - react-router-dom
 
+  '@khanacademy/wonder-blocks-typography@3.2.0(aphrodite@1.2.5)(react-dom@18.3.1(react@18.3.1))(react-router-dom@5.3.4(react@18.3.1))(react-router@6.30.0(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@khanacademy/wonder-blocks-core': 12.2.1(aphrodite@1.2.5)(react-dom@18.3.1(react@18.3.1))(react-router-dom@5.3.4(react@18.3.1))(react-router@6.30.0(react@18.3.1))(react@18.3.1)
+      '@khanacademy/wonder-blocks-tokens': 10.0.0(aphrodite@1.2.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      aphrodite: 1.2.5
+      react: 18.3.1
+    transitivePeerDependencies:
+      - react-dom
+      - react-router
+      - react-router-dom
+
   '@khanacademy/wonder-stuff-core@1.5.4': {}
 
   '@manypkg/find-root@1.1.0':
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 6410f727b04..da7b5bf84fb 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -24,6 +24,7 @@ catalog:
     "@khanacademy/wonder-blocks-form": 7.1.10
     "@khanacademy/wonder-blocks-icon-button": 9.0.4
     "@khanacademy/wonder-blocks-icon": 5.1.3
+    "@khanacademy/wonder-blocks-labeled-field": 3.0.6
     "@khanacademy/wonder-blocks-layout": 3.1.9
     "@khanacademy/wonder-blocks-link": 9.0.3
     "@khanacademy/wonder-blocks-pill": 3.1.10


Reviews:
user: login: jeremywiebe
body: Looks reasonable to me.  Just some deps cleanup to address and then it's good to go! :) 
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-14T22:42:55Z
user: login: rgpass
body: LGTM! 👏 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T13:45:04Z

Review comments:
diff_hunk: @@ -59,6 +59,7 @@
         "@khanacademy/wonder-blocks-form": "catalog:",
         "@khanacademy/wonder-blocks-icon": "catalog:",
         "@khanacademy/wonder-blocks-icon-button": "catalog:",
+        "@khanacademy/wonder-blocks-labeled-field": "catalog:",
path: packages/perseus-editor/package.json
user: login: jeremywiebe
body: I don't see any `perseus-editor` changes in this PR, so is adding this dependency here needed?
created_at: 2025-05-14T22:19:59Z
author_association: COLLABORATOR
diff_hunk: @@ -65,6 +65,7 @@
         "@khanacademy/wonder-blocks-form": "catalog:",
         "@khanacademy/wonder-blocks-icon-button": "catalog:",
         "@khanacademy/wonder-blocks-icon": "catalog:",
+        "@khanacademy/wonder-blocks-labeled-field": "catalog:",
path: packages/perseus/package.json
user: login: jeremywiebe
body: We should list this dependency in the `peerDependencies` of this package also. Some commentary that might help: https://khanacademy.atlassian.net/wiki/spaces/LC/pages/2473951425/Should+Perseus+dependencies+be+peer+dependencies

created_at: 2025-05-14T22:21:24Z
author_association: COLLABORATOR
diff_hunk: @@ -20,6 +20,7 @@
         "@khanacademy/wonder-blocks-button": "catalog:",
         "@khanacademy/wonder-blocks-core": "catalog:",
         "@khanacademy/wonder-blocks-icon": "catalog:",
+        "@khanacademy/wonder-blocks-labeled-field": "catalog:",
path: package.json
user: login: jeremywiebe
body: We also don't need this as a dependency here. I need to clean these up, but we should only have tooling and storybook-required dependencies listed here.  Each package defines the deps it needs in its own `package.json`. Sorry for the confusion. 
created_at: 2025-05-14T22:28:24Z
author_association: COLLABORATOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-14T19:58:00Z
body: **Size Change:** +78 B (+0.02%) 

**Total Size:** 469 kB

| Filename | Size | Change |
| :--- | :---: | :---: |
| `packages/perseus/dist/es/index.js` | 200 kB | +78 B (+0.04%) |

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

| Filename | Size |
| :--- | :---: |
| `packages/kas/dist/es/index.js` | 20.7 kB |
| `packages/keypad-context/dist/es/index.js` | 1 kB |
| `packages/kmath/dist/es/index.js` | 5.98 kB |
| `packages/math-input/dist/es/index.js` | 98.6 kB |
| `packages/math-input/dist/es/strings.js` | 1.61 kB |
| `packages/perseus-core/dist/es/index.js` | 19.2 kB |
| `packages/perseus-editor/dist/es/index.js` | 89.6 kB |
| `packages/perseus-linter/dist/es/index.js` | 7.05 kB |
| `packages/perseus-score/dist/es/index.js` | 9.04 kB |
| `packages/perseus-utils/dist/es/index.js` | 403 B |
| `packages/perseus/dist/es/strings.js` | 7.55 kB |
| `packages/pure-markdown/dist/es/index.js` | 1.22 kB |
| `packages/simple-markdown/dist/es/index.js` | 6.71 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-05-14T19:58:39Z
body: # npm Snapshot: Published

Good news!! We've packaged up the latest commit from this PR (6be0c551) and published it to npm. You
can install it using the tag `PR2486`.

Example:
```sh
pnpm add @khanacademy/perseus@PR2486
```

If you are working in Khan Academy's webapp, you can run the below command. 
If this is for backend code, change the static flag (-s) from y/yes to n/no.
```sh
./dev/tools/bump_perseus_version.js -s y -t PR2486
```
author_association: CONTRIBUTOR