From a32335c91dbd5e203d504c1e6c0cf5b57020bd29 Mon Sep 17 00:00:00 2001 From: Maribeth Moffatt Date: Thu, 23 Jul 2026 12:49:18 -0400 Subject: [PATCH 1/2] feat: allow translated field labels, add docs --- packages/blockly/core/field.ts | 9 +- .../interfaces/i_json_block_definition.ts | 11 + .../blockly/tests/mocha/block_json_test.js | 90 +++++++ packages/blockly/tests/mocha/field_test.js | 20 ++ .../define/structure-js.mdx | 219 ++++++++++++++++++ .../define/structure-json.mdx | 103 ++++++++ .../images/structure-js/aria-input-label.png | Bin 0 -> 6578 bytes 7 files changed, 449 insertions(+), 3 deletions(-) create mode 100644 packages/docs/static/images/structure-js/aria-input-label.png diff --git a/packages/blockly/core/field.ts b/packages/blockly/core/field.ts index b254e6704a6..7e13d6c5d29 100644 --- a/packages/blockly/core/field.ts +++ b/packages/blockly/core/field.ts @@ -254,7 +254,7 @@ export abstract class Field this.setTooltip(parsing.replaceMessageReferences(config.tooltip)); } if (config.ariaTypeName) { - this.ariaTypeName = config.ariaTypeName; + this.setAriaTypeName(config.ariaTypeName); } } @@ -322,12 +322,15 @@ export abstract class Field * Sets the ARIA-friendly label representation of this field's type. * * Implementations are responsible for, and encouraged to, set a localized - * version of the ARIA representation of the field's type. + * version of the ARIA representation of the field's type. To that end, the + * provided value may contain message references of the form `%{BKY_...}` + * (e.g. `%{BKY_MY_FIELD_ARIA_TYPE}`), which are replaced with the + * corresponding `Blockly.Msg` value. * * @param ariaTypeName An ARIA representation of the field's type. */ setAriaTypeName(ariaTypeName: string) { - this.ariaTypeName = ariaTypeName; + this.ariaTypeName = parsing.replaceMessageReferences(ariaTypeName); } /** diff --git a/packages/blockly/core/interfaces/i_json_block_definition.ts b/packages/blockly/core/interfaces/i_json_block_definition.ts index 0c2cef23582..135d3082e27 100644 --- a/packages/blockly/core/interfaces/i_json_block_definition.ts +++ b/packages/blockly/core/interfaces/i_json_block_definition.ts @@ -71,28 +71,39 @@ interface UnknownArg { [key: string]: unknown; } +/** + * A custom ARIA label for the input, used in place of the label derived from + * the input's preceding fields. May contain message references of the form + * `%{BKY_...}`, which are replaced with the corresponding `Blockly.Msg` value. + */ +type AriaLabelText = string; + /** Input args */ interface InputValueArg { type: 'input_value'; name?: string; check?: string | string[]; align?: Align; + ariaLabelText?: AriaLabelText; } interface InputStatementArg { type: 'input_statement'; name?: string; check?: string | string[]; + ariaLabelText?: AriaLabelText; } interface InputDummyArg { type: 'input_dummy'; name?: string; + ariaLabelText?: AriaLabelText; } interface InputEndRowArg { type: 'input_end_row'; name?: string; + ariaLabelText?: AriaLabelText; } /** Field args */ diff --git a/packages/blockly/tests/mocha/block_json_test.js b/packages/blockly/tests/mocha/block_json_test.js index d645cbd2a75..02ed05a012d 100644 --- a/packages/blockly/tests/mocha/block_json_test.js +++ b/packages/blockly/tests/mocha/block_json_test.js @@ -805,5 +805,95 @@ suite('Block JSON initialization', function () { delete Blockly.Blocks['test_block']; delete Blockly.Msg['CUSTOM_ROLE_DESCRIPTION']; }); + test('Custom input aria label', function () { + const testBlockDefinition = { + 'type': 'test_block', + 'message0': 'test %1', + 'args0': [ + { + 'type': 'input_value', + 'name': 'INPUT', + 'ariaLabelText': 'Custom input label', + }, + ], + }; + Blockly.common.defineBlocksWithJsonArray([testBlockDefinition]); + const block = this.workspace.newBlock('test_block'); + assert.equal( + block.getInput('INPUT').getAriaLabelText(), + 'Custom input label', + 'Expected getAriaLabelText to return the custom label.', + ); + delete Blockly.Blocks['test_block']; + }); + test('Custom input aria label with message reference', function () { + const testBlockDefinition = { + 'type': 'test_block', + 'message0': 'test %1', + 'args0': [ + { + 'type': 'input_value', + 'name': 'INPUT', + 'ariaLabelText': '%{BKY_CUSTOM_INPUT_LABEL}', + }, + ], + }; + Blockly.Msg['CUSTOM_INPUT_LABEL'] = 'Custom input label'; + Blockly.common.defineBlocksWithJsonArray([testBlockDefinition]); + const block = this.workspace.newBlock('test_block'); + assert.equal( + block.getInput('INPUT').getAriaLabelText(), + 'Custom input label', + 'Expected getAriaLabelText to return the custom label.', + ); + delete Blockly.Blocks['test_block']; + delete Blockly.Msg['CUSTOM_INPUT_LABEL']; + }); + test('Custom field aria type name', function () { + const testBlockDefinition = { + 'type': 'test_block', + 'message0': 'test %1', + 'args0': [ + { + 'type': 'field_number', + 'name': 'FIELD', + 'value': 100, + 'ariaTypeName': 'speed', + }, + ], + }; + Blockly.common.defineBlocksWithJsonArray([testBlockDefinition]); + const block = this.workspace.newBlock('test_block'); + assert.equal( + block.getField('FIELD').getAriaTypeName(), + 'speed', + 'Expected getAriaTypeName to return the custom type name.', + ); + delete Blockly.Blocks['test_block']; + }); + test('Custom field aria type name with message reference', function () { + const testBlockDefinition = { + 'type': 'test_block', + 'message0': 'test %1', + 'args0': [ + { + 'type': 'field_number', + 'name': 'FIELD', + 'value': 100, + 'ariaTypeName': '%{BKY_CUSTOM_FIELD_TYPE}', + }, + ], + }; + Blockly.Msg['CUSTOM_FIELD_TYPE'] = 'speed'; + Blockly.common.defineBlocksWithJsonArray([testBlockDefinition]); + const block = this.workspace.newBlock('test_block'); + assert.equal( + block.getField('FIELD').getAriaTypeName(), + 'speed', + 'Expected getAriaTypeName to return the custom type name.', + ); + delete Blockly.Blocks['test_block']; + delete Blockly.Msg['CUSTOM_FIELD_TYPE']; + }); }); }); diff --git a/packages/blockly/tests/mocha/field_test.js b/packages/blockly/tests/mocha/field_test.js index e01369a2462..adfc6b43c97 100644 --- a/packages/blockly/tests/mocha/field_test.js +++ b/packages/blockly/tests/mocha/field_test.js @@ -851,6 +851,26 @@ suite('Abstract Fields', function () { const field = CustomField.fromJson({ariaTypeName: 'text input'}); assert.equal(field.getAriaTypeName(), 'text input'); }); + + suite('W/ Msg References', function () { + setup(function () { + addMessageToCleanup(this.sharedCleanup, 'ARIA_TYPE'); + Blockly.Msg['ARIA_TYPE'] = 'speed'; + }); + + test('setAriaTypeName resolves message references', function () { + const field = new TestField(); + field.setAriaTypeName('%{BKY_ARIA_TYPE}'); + assert.equal(field.getAriaTypeName(), 'speed'); + }); + + test('Configured ariaTypeName resolves message references', function () { + const field = new TestField('value', { + ariaTypeName: '%{BKY_ARIA_TYPE}', + }); + assert.equal(field.getAriaTypeName(), 'speed'); + }); + }); }); suite('getAriaValue', function () { diff --git a/packages/docs/docs/guides/create-custom-blocks/define/structure-js.mdx b/packages/docs/docs/guides/create-custom-blocks/define/structure-js.mdx index 537f2c199c9..610e1f6525e 100644 --- a/packages/docs/docs/guides/create-custom-blocks/define/structure-js.mdx +++ b/packages/docs/docs/guides/create-custom-blocks/define/structure-js.mdx @@ -72,6 +72,88 @@ All of the `appendInput` methods (both generic and non-generic) return the input object so that they can be further configured using method chaining. There are three built-in methods used for configuring inputs. +### Input labels + +Inputs on blocks have default accessibility labels that are derived from the +surrounding text on the block. For example, the "in list" value input on the +`for each` block above is labeled `in list`, which comes from the label field +that precedes it. A screen reader uses this label to describe the input's empty +connection and to give context to any block plugged into it. + +For some custom blocks, this derived label does not provide enough information. +Consider a block that builds a coordinate point: + +{/* TODO: screenshot of the "point (x, y)" block below, with the second value +input highlighted. */} +A block with the label "point (", a value input, the label ",", another value input, and the label ")". + +Both value inputs are preceded only by punctuation, so the highlighted input's +derived label would read `,`, which tells assistive technology users nothing +about what belongs there. To provide more information, you can set custom labels. +In this case, "x coordinate" and "y coordinate" would be appropriate. + +You can set the label for an input by calling `setAriaLabelProvider` on the +input in the block definition. The provider can be either a string or a function +that returns a string, to support dynamic labels. + + + + ```js + init: function() { + // ... + this.appendValueInput('X') + .setAriaLabelProvider('x coordinate') + .appendField('point ('); + this.appendValueInput('Y') + .setAriaLabelProvider('y coordinate') + .appendField(','); + this.appendDummyInput() + .appendField(')'); + } + ``` + + + +Passing a function instead of a string lets you build the label dynamically, for +example from the block's current field values. The function receives the +`Input` and returns a string (or `null` to fall back to the default label). + + + + ```js + init: function() { + // ... + this.appendValueInput('DURATION') + .setAriaLabelProvider((input) => { + const units = input.getSourceBlock().getFieldValue('UNITS'); + return `duration in ${units}`; + }) + .appendField('wait'); + } + ``` + + + +To provide a localized label, pass a string containing a +[`Blockly.Msg`](/guides/configure/translations) reference of the form +`%{BKY_...}`. Blockly replaces the reference with the corresponding message at +render time. + + + + ```js + init: function() { + // ... + this.appendValueInput('X') + .setAriaLabelProvider('%{BKY_POINT_X_ARIA_LABEL}') + .appendField('point ('); + } + ``` + + + +You can set the same labels [in JSON](/guides/create-custom-blocks/define/structure-json#input-labels). + ## Append fields Once an input has been created and appended to a block with `appendInput`, one @@ -120,6 +202,76 @@ The `appendField('hello')` call is actually a shortcut for using an explicit The only time one would wish to use the constructor is when specifying a class name so that the label may be styled using a CSS rule. +### Field labels + +Similar to inputs, every field has an accessibility label. The default label is +determined by the type and value of the field, in the form +`: `. For example, a `FieldNumber` with the value 123 is labeled +`number: 123`. + +For some blocks, you might want to use a more specific description for the type +of field than simply "number" or "dropdown". A block that sets the speed of a +motor might want to label its number field as "speed", for example, so that a +screen reader announces `speed: 100` instead of `number: 100`. + +There are two ways to set the type name for a particular field instance on a +block. + +The simplest is to pass an `ariaTypeName` in the field's constructor +configuration object. This object is the field's last constructor argument (its +position depends on the field type), and is where you configure other field +options too. + + + + ```js + init: function() { + // ... + this.appendDummyInput() + .appendField('set motor speed to') + .appendField( + new Blockly.FieldNumber( + 100, // value + 0, // min + 255, // max + undefined, // precision + undefined, // validator + {ariaTypeName: 'speed'}, // config + ), + 'SPEED'); + } + ``` + + + +Alternatively, call `setAriaTypeName` on the field: + + + + ```js + init: function() { + // ... + const speed = new Blockly.FieldNumber(100, 0, 255); + speed.setAriaTypeName('speed'); + this.appendDummyInput() + .appendField('set motor speed to') + .appendField(speed, 'SPEED'); + } + ``` + + + +Either way, the type name may contain a +[`Blockly.Msg`](/guides/configure/translations) reference of the form +`%{BKY_...}` for localization, the same as input labels and role descriptions. + +To set the label for all instances of a particular type of field, override +`getAriaTypeName` in a [custom +field](/guides/create-custom-blocks/fields/customizing-fields/creating#aria-value) +instead of setting it per instance. + +You can set the type name [in JSON](/guides/create-custom-blocks/define/structure-json#field-labels) as well. + ## Connection checks @@ -163,3 +315,70 @@ and right are reversed. Thus `Blockly.inputs.Align.RIGHT` would align fields to the left. [custom-inputs]: /guides/create-custom-blocks/inputs/creating-custom-inputs + +## Accessibility role descriptions + +All blocks have an [`aria-roledescription`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-roledescription) +so that screen readers announce the block with a useful role rather than the +default "graphics element". + +The default roles Blockly uses are based on the connections available on a +block. They are: + +- `container` (has at least one statement input) +- `value` (has an output connection) +- `statement` (all other blocks) + +These role descriptions are localized. Note that we do not include the word +"block" in the role description, as feedback from users was that including the +word "block" was too repetitive and noisy in the screen reader output. + +### Custom role descriptions + +In some situations, you may want to set a custom role description on your custom +blocks. We recommend that in most cases you use the default role descriptions +rather than setting a custom role description for every block. Generally, the +text on the block, forming the aria label for it, should tell users what the +block does. The role description gives users a hint about the shape of the block +and what kind of connections it can form. + +If you have blocks that are a different shape from other blocks, that may be a +good use case for setting a custom role description. For example, boolean blocks +in the Zelos renderer have a hexagonal shape and can only be put into certain +input connections, so they are different from other "value" blocks. You can set +a custom role description by calling `setAriaRoleDescriptionProvider` in the +block definition. As with input labels, you can pass either a string or a +function that returns a string. + + + + ```js + init: function() { + // ... + this.setAriaRoleDescriptionProvider('boolean'); + } + ``` + + + +To localize the role description, pass a string containing a `Blockly.Msg` +reference of the form `%{BKY_...}`; Blockly replaces it at render time. + + + + ```js + init: function() { + // ... + this.setAriaRoleDescriptionProvider('%{BKY_BOOLEAN_ROLE_DESCRIPTION}'); + } + ``` + + + +You can set a custom role description [in JSON](/guides/create-custom-blocks/define/structure-json#role-descriptions) as well. + +:::note +By default, Blockly does not set boolean blocks in Zelos to a "boolean" role +description, as not all users will be familiar with this term. However, you can +do this in your own application if the term makes sense for your users. +::: diff --git a/packages/docs/docs/guides/create-custom-blocks/define/structure-json.mdx b/packages/docs/docs/guides/create-custom-blocks/define/structure-json.mdx index 471a9d2560f..a191467db58 100644 --- a/packages/docs/docs/guides/create-custom-blocks/define/structure-json.mdx +++ b/packages/docs/docs/guides/create-custom-blocks/define/structure-json.mdx @@ -394,5 +394,108 @@ An `alt` object may have its own `alt` object, thus allowing for chaining. Ultimately, if Blockly cannot create an object in the `args0` array (after attempting any `alt` objects) then that object is simply skipped. +## Accessibility labels and roles + +Blockly generates accessibility labels and roles for blocks, inputs, and fields +so that screen readers can describe them. You can customize these from JSON. For +a fuller explanation of what each one controls, see the corresponding sections +of the [JavaScript structure +guide](/guides/create-custom-blocks/define/structure-js#input-labels). + +Unlike the JavaScript API, the JSON properties accept only strings (not +functions). All of them support +[`Blockly.Msg`](/guides/configure/translations) references of the form +`%{BKY_...}`, which are replaced at render time. + +### Input labels + +An input's accessibility label is derived from the label fields that precede it. +When that derived label isn't descriptive enough, add an `ariaLabelText` +property to the input's argument object to override it. For example, the two +value inputs in this coordinate block are preceded only by punctuation, so each +one gets a custom label: + + + + ```js + { + "message0": "point ( %1 , %2 )", + "args0": [ + { + "type": "input_value", + "name": "X", + "ariaLabelText": "x coordinate" + }, + { + "type": "input_value", + "name": "Y", + "ariaLabelText": "y coordinate" + } + ] + } + ``` + + + +### Field labels + +Every field has a default accessibility label of the form `: ` +(for example, `number: 100`). To use a more specific type name for a particular +field, add an `ariaTypeName` property to the field's argument object. The motor +block below is announced as `speed: 100` instead of `number: 100`: + + + + ```js + { + "message0": "set motor speed to %1", + "args0": [ + { + "type": "field_number", + "name": "SPEED", + "value": 100, + "ariaTypeName": "speed" + } + ] + } + ``` + + + +To set the type name for all instances of a field type, override +`getAriaTypeName` in a [custom +field](/guides/create-custom-blocks/fields/customizing-fields/creating#aria-value) +instead. + +### Role descriptions + +Every block has an +[`aria-roledescription`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-roledescription) +that describes its shape. By default it is `container`, `value`, or `statement`, +based on the block's connections. To override it, add a top-level +`ariaRoleDescription` property to the block definition. For example, a +hexagonal boolean block in the Zelos renderer might use: + + + + ```js + { + "message0": "%1 and %2", + "args0": [ + {"type": "input_value", "name": "A", "check": "Boolean"}, + {"type": "input_value", "name": "B", "check": "Boolean"} + ], + "output": "Boolean", + "ariaRoleDescription": "boolean" + } + ``` + + + +In most cases you should rely on the default role descriptions rather than +setting a custom one for every block. See [Custom role +descriptions](/guides/create-custom-blocks/define/structure-js#custom-role-descriptions) +for guidance on when a custom role description is worthwhile. + [custom-inputs]: /guides/create-custom-blocks/inputs/creating-custom-inputs [custom-fields]: /guides/create-custom-blocks/fields/customizing-fields/creating diff --git a/packages/docs/static/images/structure-js/aria-input-label.png b/packages/docs/static/images/structure-js/aria-input-label.png new file mode 100644 index 0000000000000000000000000000000000000000..c52fb40986d691d984383acf19b1fddd0999a270 GIT binary patch literal 6578 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 Date: Thu, 23 Jul 2026 14:17:03 -0400 Subject: [PATCH 2/2] chore: touch up docs --- .../docs/guides/create-custom-blocks/define/structure-js.mdx | 2 -- .../docs/guides/create-custom-blocks/define/structure-json.mdx | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/docs/docs/guides/create-custom-blocks/define/structure-js.mdx b/packages/docs/docs/guides/create-custom-blocks/define/structure-js.mdx index 610e1f6525e..b4c40316d74 100644 --- a/packages/docs/docs/guides/create-custom-blocks/define/structure-js.mdx +++ b/packages/docs/docs/guides/create-custom-blocks/define/structure-js.mdx @@ -83,8 +83,6 @@ connection and to give context to any block plugged into it. For some custom blocks, this derived label does not provide enough information. Consider a block that builds a coordinate point: -{/* TODO: screenshot of the "point (x, y)" block below, with the second value -input highlighted. */} A block with the label "point (", a value input, the label ",", another value input, and the label ")". Both value inputs are preceded only by punctuation, so the highlighted input's diff --git a/packages/docs/docs/guides/create-custom-blocks/define/structure-json.mdx b/packages/docs/docs/guides/create-custom-blocks/define/structure-json.mdx index a191467db58..530b8b3a9bf 100644 --- a/packages/docs/docs/guides/create-custom-blocks/define/structure-json.mdx +++ b/packages/docs/docs/guides/create-custom-blocks/define/structure-json.mdx @@ -437,6 +437,8 @@ one gets a custom label: +A block with the label "point (", a value input, the label ",", another value input, and the label ")". + ### Field labels Every field has a default accessibility label of the form `: `