Skip to content

[Bug]: A choice containing only an image has no text, so it announces as blank #1634

Description

@dqnykamp

Description

A <choice> whose content is an <image> has no text representation, so anything that reads a choice as text gets a blank instead of the image's description.

Noticed while fixing #1613 (inline <choiceInput> announcing [object Object]). That fix gives each option an accessible name from choiceTexts, which is built from <choice>'s text state variable via textFromChildren. textFromComponent returns " " for any component that has no string text state variable:

https://github.com/Doenet/DoenetML/blob/main/packages/doenetml-worker-javascript/src/utils/text.ts#L1-L11

<image> is such a component. So an image-only choice went from announcing "[object Object]" to announcing nothing at all — better, but still not usable.

Reproduce

<choiceInput inline name="ci">
  <choice><image source="doenet:cid=..."><shortDescription>A cat</shortDescription></image></choice>
  <choice><image source="doenet:cid=..."><shortDescription>A dog</shortDescription></image></choice>
</choiceInput>

A screen reader announces the options as blank. $ci.choiceTexts is [" ", " "].

Expected

The choices should read as "A cat" and "A dog" — the text <image> already renders as its alt attribute.

Suggested fix

<image> already computes exactly the right string in its shortDescription state variable (packages/doenetml-worker-javascript/src/components/Image.js), which is what the renderer passes to alt, and which is empty for a decorative image. Expose it as text:

stateVariableDefinitions.text = {
    description: "The image's short description, as a text string.",
    public: true,
    shadowingInstructions: { createComponentOfType: "text" },
    returnDependencies: () => ({
        shortDescription: {
            dependencyType: "stateVariable",
            variableName: "shortDescription",
        },
    }),
    definition: ({ dependencyValues }) => ({
        setValue: { text: dependencyValues.shortDescription },
    }),
};

textFromComponent picks it up with no further changes.

Why this needs its own PR rather than riding along with the a11y fix

choiceTexts is public and does more than name options — it also feeds indicesMatchedByBoundValue and selectedValues in ChoiceInput.js. So an image choice would start matching an <answer> by its description text. That is arguably the right behavior, but it is an answer-checking change and wants its own worker tests.

Giving <image> a text state variable also affects textFromChildren everywhere else an image can appear, so it is worth a deliberate look at the other call sites rather than a drive-by.

Related

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    accessibilityImprovement need to make Doenet accessible

    Type

    Fields

    Priority

    Medium

    Effort

    Medium

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions