diff options
| author | Nolan Darilek <[email protected]> | 2026-01-22 05:10:53 -0600 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-01-22 05:10:53 -0600 |
| commit | 3435327bc074a7ba8c3fe8939c97de54bbdefd65 (patch) | |
| tree | d906b07c16e8e90808e5f78b72f72e8737f53eeb | |
| parent | 8a043edfd5a504421301cc183eedf4ecdb4c57b0 (diff) | |
| download | opencode-3435327bc074a7ba8c3fe8939c97de54bbdefd65.tar.gz opencode-3435327bc074a7ba8c3fe8939c97de54bbdefd65.zip | |
fix(app): session screen accessibility improvements (#9907)
19 files changed, 120 insertions, 75 deletions
diff --git a/packages/app/src/components/dialog-connect-provider.tsx b/packages/app/src/components/dialog-connect-provider.tsx index 4ec4c8daa..d8d4ad9c2 100644 --- a/packages/app/src/components/dialog-connect-provider.tsx +++ b/packages/app/src/components/dialog-connect-provider.tsx @@ -143,7 +143,7 @@ export function DialogConnectProvider(props: { provider: string }) { } return ( - <Dialog title={<IconButton tabIndex={-1} icon="arrow-left" variant="ghost" onClick={goBack} />}> + <Dialog title={<IconButton tabIndex={-1} icon="arrow-left" variant="ghost" onClick={goBack} aria-label="Go back" />}> <div class="flex flex-col gap-6 px-2.5 pb-3"> <div class="px-2.5 flex gap-4 items-center"> <ProviderIcon id={props.provider as IconName} class="size-5 shrink-0 icon-strong-base" /> diff --git a/packages/app/src/components/dialog-edit-project.tsx b/packages/app/src/components/dialog-edit-project.tsx index acf146ef5..cf5535a67 100644 --- a/packages/app/src/components/dialog-edit-project.tsx +++ b/packages/app/src/components/dialog-edit-project.tsx @@ -193,6 +193,8 @@ export function DialogEditProject(props: { project: LocalProject }) { {(color) => ( <button type="button" + aria-label={`Select ${color} color`} + aria-pressed={store.color === color} classList={{ "flex items-center justify-center size-10 p-0.5 rounded-lg overflow-hidden transition-colors cursor-default": true, "bg-transparent border-2 border-icon-strong-base hover:bg-surface-base-hover": diff --git a/packages/app/src/components/dialog-select-model.tsx b/packages/app/src/components/dialog-select-model.tsx index 4fd8af1b3..5569d7780 100644 --- a/packages/app/src/components/dialog-select-model.tsx +++ b/packages/app/src/components/dialog-select-model.tsx @@ -1,5 +1,5 @@ import { Popover as Kobalte } from "@kobalte/core/popover" -import { Component, createMemo, createSignal, JSX, Show } from "solid-js" +import { Component, ComponentProps, createMemo, createSignal, JSX, Show, ValidComponent } from "solid-js" import { useLocal } from "@/context/local" import { useDialog } from "@opencode-ai/ui/context/dialog" import { popularProviders } from "@/hooks/use-providers" @@ -86,10 +86,12 @@ const ModelList: Component<{ ) } -export const ModelSelectorPopover: Component<{ +export function ModelSelectorPopover<T extends ValidComponent = "div">(props: { provider?: string - children: JSX.Element -}> = (props) => { + children?: JSX.Element + triggerAs?: T + triggerProps?: ComponentProps<T> +}) { const [open, setOpen] = createSignal(false) const dialog = useDialog() @@ -101,7 +103,9 @@ export const ModelSelectorPopover: Component<{ return ( <Kobalte open={open()} onOpenChange={setOpen} placement="top-start" gutter={8}> - <Kobalte.Trigger as="div">{props.children}</Kobalte.Trigger> + <Kobalte.Trigger as={props.triggerAs ?? "div"} {...(props.triggerProps as any)}> + {props.children} + </Kobalte.Trigger> <Kobalte.Portal> <Kobalte.Content class="w-72 h-80 flex flex-col rounded-md border border-border-base bg-surface-raised-stronger-non-alpha shadow-md z-50 outline-none overflow-hidden"> <Kobalte.Title class="sr-only">{language.t("dialog.model.select.title")}</Kobalte.Title> diff --git a/packages/app/src/components/dialog-select-server.tsx b/packages/app/src/components/dialog-select-server.tsx index bb0ad5b43..a8e7ca4f7 100644 --- a/packages/app/src/components/dialog-select-server.tsx +++ b/packages/app/src/components/dialog-select-server.tsx @@ -158,6 +158,7 @@ export function DialogSelectServer() { icon="circle-x" variant="ghost" class="bg-transparent transition-opacity shrink-0 hover:scale-110" + aria-label="Remove server" onClick={(e) => { e.stopPropagation() handleRemove(i) diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index 3bd7856ee..825f2b116 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -1487,6 +1487,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => { variant="ghost" class="h-6 w-6" onClick={() => prompt.context.removeActive()} + aria-label="Remove active file from context" /> </div> )} @@ -1524,6 +1525,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => { variant="ghost" class="h-6 w-6" onClick={() => prompt.context.remove(item.key)} + aria-label="Remove file from context" /> </div> )} @@ -1556,6 +1558,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => { type="button" onClick={() => removeImageAttachment(attachment.id)} class="absolute -top-1.5 -right-1.5 size-5 rounded-full bg-surface-raised-stronger-non-alpha border border-border-base flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity hover:bg-surface-raised-base-hover" + aria-label="Remove attachment" > <Icon name="close" class="size-3 text-text-weak" /> </button> @@ -1574,6 +1577,13 @@ export const PromptInput: Component<PromptInputProps> = (props) => { editorRef = el props.ref?.(el) }} + role="textbox" + aria-multiline="true" + aria-label={ + store.mode === "shell" + ? language.t("prompt.placeholder.shell") + : language.t("prompt.placeholder.normal", { example: language.t(EXAMPLES[store.placeholder]) }) + } contenteditable="true" onInput={handleInput} onPaste={handlePaste} @@ -1638,21 +1648,22 @@ export const PromptInput: Component<PromptInputProps> = (props) => { </TooltipKeybind> } > - <ModelSelectorPopover> - <TooltipKeybind - placement="top" - title={language.t("command.model.choose")} - keybind={command.keybind("model.choose")} + <TooltipKeybind + placement="top" + title={language.t("command.model.choose")} + keybind={command.keybind("model.choose")} + > + <ModelSelectorPopover + triggerAs={Button} + triggerProps={{ variant: "ghost" }} > - <Button as="div" variant="ghost"> - <Show when={local.model.current()?.provider?.id}> - <ProviderIcon id={local.model.current()!.provider.id as IconName} class="size-4 shrink-0" /> - </Show> - {local.model.current()?.name ?? language.t("dialog.model.select.title")} - <Icon name="chevron-down" size="small" /> - </Button> - </TooltipKeybind> - </ModelSelectorPopover> + <Show when={local.model.current()?.provider?.id}> + <ProviderIcon id={local.model.current()!.provider.id as IconName} class="size-4 shrink-0" /> + </Show> + {local.model.current()?.name ?? language.t("dialog.model.select.title")} + <Icon name="chevron-down" size="small" /> + </ModelSelectorPopover> + </TooltipKeybind> </Show> <Show when={local.model.variant.list().length > 0}> <TooltipKeybind @@ -1683,6 +1694,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => { "text-text-base": !permission.isAutoAccepting(params.id!, sdk.directory), "hover:bg-surface-success-base": permission.isAutoAccepting(params.id!, sdk.directory), }} + aria-label="Toggle auto-accept permissions" + aria-pressed={permission.isAutoAccepting(params.id!, sdk.directory)} > <Icon name="chevron-double-right" @@ -1711,7 +1724,13 @@ export const PromptInput: Component<PromptInputProps> = (props) => { <SessionContextUsage /> <Show when={store.mode === "normal"}> <Tooltip placement="top" value={language.t("prompt.action.attachFile")}> - <Button type="button" variant="ghost" class="size-6" onClick={() => fileInputRef.click()}> + <Button + type="button" + variant="ghost" + class="size-6" + onClick={() => fileInputRef.click()} + aria-label="Attach file" + > <Icon name="photo" class="size-4.5" /> </Button> </Tooltip> @@ -1743,6 +1762,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => { icon={working() ? "stop" : "arrow-up"} variant="primary" class="h-6 w-4.5" + aria-label={working() ? "Stop" : "Send message"} /> </Tooltip> </div> diff --git a/packages/app/src/components/session-context-usage.tsx b/packages/app/src/components/session-context-usage.tsx index 53148d416..677e29dbb 100644 --- a/packages/app/src/components/session-context-usage.tsx +++ b/packages/app/src/components/session-context-usage.tsx @@ -96,7 +96,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) { <Switch> <Match when={variant() === "indicator"}>{circle()}</Match> <Match when={true}> - <Button type="button" variant="ghost" class="size-6" onClick={openContext}> + <Button type="button" variant="ghost" class="size-6" onClick={openContext} aria-label="View context usage"> {circle()} </Button> </Match> diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx index 98b9635c5..f2ffa3ec5 100644 --- a/packages/app/src/components/session/session-header.tsx +++ b/packages/app/src/components/session/session-header.tsx @@ -135,6 +135,7 @@ export function SessionHeader() { type="button" class="hidden md:flex w-[320px] p-1 pl-1.5 items-center gap-2 justify-between rounded-md border border-border-weak-base bg-surface-raised-base transition-colors cursor-default hover:bg-surface-raised-base-hover focus:bg-surface-raised-base-hover active:bg-surface-raised-base-active" onClick={() => command.trigger("file.open")} + aria-label="Search files" > <div class="flex min-w-0 flex-1 items-center gap-2 overflow-visible"> <Icon name="magnifying-glass" size="normal" class="icon-base shrink-0" /> @@ -184,6 +185,10 @@ export function SessionHeader() { variant="ghost" class="group/review-toggle size-6 p-0" onClick={() => view().reviewPanel.toggle()} + aria-label="Toggle review panel" + aria-expanded={view().reviewPanel.opened()} + aria-controls="review-panel" + tabIndex={showReview() ? 0 : -1} > <div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0"> <Icon @@ -214,6 +219,9 @@ export function SessionHeader() { variant="ghost" class="group/terminal-toggle size-6 p-0" onClick={() => view().terminal.toggle()} + aria-label="Toggle terminal" + aria-expanded={view().terminal.opened()} + aria-controls="terminal-panel" > <div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0"> <Icon @@ -235,32 +243,23 @@ export function SessionHeader() { </Button> </TooltipKeybind> </div> - <div - class="flex items-center" - classList={{ - "opacity-0 pointer-events-none": !showShare(), - }} - aria-hidden={!showShare()} - > - <Popover - title={language.t("session.share.popover.title")} - description={ - shareUrl() - ? language.t("session.share.popover.description.shared") - : language.t("session.share.popover.description.unshared") - } - trigger={ - <Tooltip class="shrink-0" value={language.t("command.session.share")}> - <Button - variant="secondary" - classList={{ "rounded-r-none": shareUrl() !== undefined }} - style={{ scale: 1 }} - > - {language.t("session.share.action.share")} - </Button> - </Tooltip> - } - > + <Show when={showShare()}> + <div class="flex items-center"> + <Popover + title={language.t("session.share.popover.title")} + description={ + shareUrl() + ? language.t("session.share.popover.description.shared") + : language.t("session.share.popover.description.unshared") + } + triggerAs={Button} + triggerProps={{ + variant: "secondary", + classList: { "rounded-r-none": shareUrl() !== undefined }, + style: { scale: 1 }, + }} + trigger={language.t("session.share.action.share")} + > <div class="flex flex-col gap-2"> <Show when={shareUrl()} @@ -322,10 +321,12 @@ export function SessionHeader() { class="rounded-l-none" onClick={copyLink} disabled={state.unshare} + aria-label="Copy share link" /> </Tooltip> </Show> - </div> + </div> + </Show> </div> </Portal> )} diff --git a/packages/app/src/components/session/session-sortable-tab.tsx b/packages/app/src/components/session/session-sortable-tab.tsx index a4a434b05..6f2c044fc 100644 --- a/packages/app/src/components/session/session-sortable-tab.tsx +++ b/packages/app/src/components/session/session-sortable-tab.tsx @@ -37,7 +37,7 @@ export function SortableTab(props: { tab: string; onTabClose: (tab: string) => v value={props.tab} closeButton={ <Tooltip value={language.t("common.closeTab")} placement="bottom"> - <IconButton icon="close" variant="ghost" onClick={() => props.onTabClose(props.tab)} /> + <IconButton icon="close" variant="ghost" onClick={() => props.onTabClose(props.tab)} aria-label="Close tab" /> </Tooltip> } hideCloseButton diff --git a/packages/app/src/components/session/session-sortable-terminal-tab.tsx b/packages/app/src/components/session/session-sortable-terminal-tab.tsx index a3aaf6061..3ef395c9a 100644 --- a/packages/app/src/components/session/session-sortable-terminal-tab.tsx +++ b/packages/app/src/components/session/session-sortable-terminal-tab.tsx @@ -139,6 +139,7 @@ export function SortableTerminalTab(props: { terminal: LocalPTY; onClose?: () => e.stopPropagation() close() }} + aria-label="Close terminal" /> } > diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index befdf721d..2a050543c 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -1916,6 +1916,7 @@ export default function Layout(props: ParentProps) { "bg-surface-base-hover border border-border-weak-base": !selected() && open(), }} onClick={() => navigateToProject(props.project.worktree)} + onBlur={() => setOpen(false)} > <ProjectIcon project={props.project} notify /> </button> @@ -2343,7 +2344,8 @@ export default function Layout(props: ParentProps) { <div class="relative bg-background-base flex-1 min-h-0 flex flex-col select-none [&_input]:select-text [&_textarea]:select-text [&_[contenteditable]]:select-text"> <Titlebar /> <div class="flex-1 min-h-0 flex"> - <div + <nav + aria-label="Projects and sessions" classList={{ "hidden xl:block": true, "relative shrink-0": true, @@ -2364,7 +2366,7 @@ export default function Layout(props: ParentProps) { onCollapse={layout.sidebar.close} /> </Show> - </div> + </nav> <div class="xl:hidden"> <div classList={{ @@ -2376,7 +2378,8 @@ export default function Layout(props: ParentProps) { if (e.target === e.currentTarget) layout.mobileSidebar.hide() }} /> - <div + <nav + aria-label="Projects and sessions" classList={{ "@container fixed top-10 bottom-0 left-0 z-50 w-72 bg-background-base transition-transform duration-200 ease-out": true, "translate-x-0": layout.mobileSidebar.opened(), @@ -2385,7 +2388,7 @@ export default function Layout(props: ParentProps) { onClick={(e) => e.stopPropagation()} > <SidebarContent mobile /> - </div> + </nav> </div> <main diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 5e4eeafd5..19ee5d304 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -782,7 +782,7 @@ export default function Page() { const activeElement = document.activeElement as HTMLElement | undefined if (activeElement) { const isProtected = activeElement.closest("[data-prevent-autofocus]") - const isInput = /^(INPUT|TEXTAREA|SELECT)$/.test(activeElement.tagName) || activeElement.isContentEditable + const isInput = /^(INPUT|TEXTAREA|SELECT|BUTTON)$/.test(activeElement.tagName) || activeElement.isContentEditable if (isProtected || isInput) return } if (dialog.active) return @@ -1404,6 +1404,7 @@ export default function Page() { <div ref={autoScroll.contentRef} + role="log" class="flex flex-col gap-32 items-start justify-start pb-[calc(var(--prompt-height,8rem)+64px)] md:pb-[calc(var(--prompt-height,10rem)+64px)] transition-[margin]" classList={{ "w-full": true, @@ -1552,7 +1553,7 @@ export default function Page() { {/* Desktop tabs panel (Review + Context + Files) - hidden on mobile */} <Show when={isDesktop() && showTabs()}> - <div class="relative flex-1 min-w-0 h-full border-l border-border-weak-base"> + <aside id="review-panel" aria-label="Review and files" class="relative flex-1 min-w-0 h-full border-l border-border-weak-base"> <DragDropProvider onDragStart={handleDragStart} onDragEnd={handleDragEnd} @@ -1586,7 +1587,7 @@ export default function Page() { value="context" closeButton={ <Tooltip value={language.t("common.closeTab")} placement="bottom"> - <IconButton icon="close" variant="ghost" onClick={() => tabs().close("context")} /> + <IconButton icon="close" variant="ghost" onClick={() => tabs().close("context")} aria-label="Close context tab" /> </Tooltip> } hideCloseButton @@ -1612,6 +1613,7 @@ export default function Page() { variant="ghost" iconSize="large" onClick={() => dialog.show(() => <DialogSelectFile />)} + aria-label="Open file" /> </TooltipKeybind> </div> @@ -1913,12 +1915,15 @@ export default function Page() { </Show> </DragOverlay> </DragDropProvider> - </div> + </aside> </Show> </div> <Show when={isDesktop() && view().terminal.opened()}> <div + id="terminal-panel" + role="region" + aria-label="Terminal" class="relative w-full flex flex-col shrink-0 border-t border-border-weak-base" style={{ height: `${layout.terminal.height()}px` }} > @@ -1990,7 +1995,7 @@ export default function Page() { keybind={command.keybind("terminal.new")} class="flex items-center" > - <IconButton icon="plus-small" variant="ghost" iconSize="large" onClick={terminal.new} /> + <IconButton icon="plus-small" variant="ghost" iconSize="large" onClick={terminal.new} aria-label="New terminal" /> </TooltipKeybind> </div> </Tabs.List> diff --git a/packages/ui/src/components/dialog.tsx b/packages/ui/src/components/dialog.tsx index 4b871732d..cff9edaf2 100644 --- a/packages/ui/src/components/dialog.tsx +++ b/packages/ui/src/components/dialog.tsx @@ -40,7 +40,7 @@ export function Dialog(props: DialogProps) { <Switch> <Match when={props.action}>{props.action}</Match> <Match when={true}> - <Kobalte.CloseButton data-slot="dialog-close-button" as={IconButton} icon="close" variant="ghost" /> + <Kobalte.CloseButton data-slot="dialog-close-button" as={IconButton} icon="close" variant="ghost" aria-label="Close" /> </Match> </Switch> </div> diff --git a/packages/ui/src/components/image-preview.tsx b/packages/ui/src/components/image-preview.tsx index 88bf38980..1ab868576 100644 --- a/packages/ui/src/components/image-preview.tsx +++ b/packages/ui/src/components/image-preview.tsx @@ -14,7 +14,7 @@ export function ImagePreview(props: ImagePreviewProps) { <div data-slot="image-preview-container"> <Kobalte.Content data-slot="image-preview-content"> <div data-slot="image-preview-header"> - <Kobalte.CloseButton data-slot="image-preview-close" as={IconButton} icon="close" variant="ghost" /> + <Kobalte.CloseButton data-slot="image-preview-close" as={IconButton} icon="close" variant="ghost" aria-label="Close" /> </div> <div data-slot="image-preview-body"> <img src={props.src} alt={props.alt ?? i18n.t("ui.imagePreview.alt")} data-slot="image-preview-image" /> diff --git a/packages/ui/src/components/list.tsx b/packages/ui/src/components/list.tsx index aeb2769d6..41e528e79 100644 --- a/packages/ui/src/components/list.tsx +++ b/packages/ui/src/components/list.tsx @@ -230,7 +230,7 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void }) /> </div> <Show when={internalFilter()}> - <IconButton icon="circle-x" variant="ghost" onClick={() => setInternalFilter("")} /> + <IconButton icon="circle-x" variant="ghost" onClick={() => setInternalFilter("")} aria-label="Clear filter" /> </Show> </div> {searchAction()} diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index d1788f1df..d639c5224 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -429,6 +429,7 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp event.stopPropagation() handleCopy() }} + aria-label={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.message.copy")} /> </Tooltip> </div> diff --git a/packages/ui/src/components/popover.tsx b/packages/ui/src/components/popover.tsx index 3262098e5..3de7cb516 100644 --- a/packages/ui/src/components/popover.tsx +++ b/packages/ui/src/components/popover.tsx @@ -1,21 +1,23 @@ import { Popover as Kobalte } from "@kobalte/core/popover" -import { ComponentProps, JSXElement, ParentProps, Show, splitProps } from "solid-js" +import { ComponentProps, JSXElement, ParentProps, Show, splitProps, ValidComponent } from "solid-js" import { IconButton } from "./icon-button" -export interface PopoverProps extends ParentProps, Omit<ComponentProps<typeof Kobalte>, "children"> { - trigger: JSXElement +export interface PopoverProps<T extends ValidComponent = "div"> extends ParentProps, Omit<ComponentProps<typeof Kobalte>, "children"> { + trigger?: JSXElement + triggerAs?: T + triggerProps?: ComponentProps<T> title?: JSXElement description?: JSXElement class?: ComponentProps<"div">["class"] classList?: ComponentProps<"div">["classList"] } -export function Popover(props: PopoverProps) { - const [local, rest] = splitProps(props, ["trigger", "title", "description", "class", "classList", "children"]) +export function Popover<T extends ValidComponent = "div">(props: PopoverProps<T>) { + const [local, rest] = splitProps(props, ["trigger", "triggerAs", "triggerProps", "title", "description", "class", "classList", "children"]) return ( <Kobalte gutter={4} {...rest}> - <Kobalte.Trigger as="div" data-slot="popover-trigger"> + <Kobalte.Trigger as={local.triggerAs ?? "div"} data-slot="popover-trigger" {...(local.triggerProps as any)}> {local.trigger} </Kobalte.Trigger> <Kobalte.Portal> @@ -30,7 +32,7 @@ export function Popover(props: PopoverProps) { <Show when={local.title}> <div data-slot="popover-header"> <Kobalte.Title data-slot="popover-title">{local.title}</Kobalte.Title> - <Kobalte.CloseButton data-slot="popover-close-button" as={IconButton} icon="close" variant="ghost" /> + <Kobalte.CloseButton data-slot="popover-close-button" as={IconButton} icon="close" variant="ghost" aria-label="Close" /> </div> </Show> <Show when={local.description}> diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx index 06e421882..1cd210499 100644 --- a/packages/ui/src/components/session-turn.tsx +++ b/packages/ui/src/components/session-turn.tsx @@ -506,13 +506,13 @@ export function SessionTurn( </Match> <Match when={true}> <Show when={attachmentParts().length > 0}> - <div data-slot="session-turn-attachments"> + <div data-slot="session-turn-attachments" aria-live="off"> <Message message={msg()} parts={attachmentParts()} /> </div> </Show> <div data-slot="session-turn-sticky" ref={setStickyRef}> {/* User Message */} - <div data-slot="session-turn-message-content"> + <div data-slot="session-turn-message-content" aria-live="off"> <Message message={msg()} parts={stickyParts()} /> </div> @@ -525,6 +525,7 @@ export function SessionTurn( variant="ghost" size="small" onClick={props.onStepsExpandedToggle ?? (() => {})} + aria-expanded={props.stepsExpanded} > <Show when={working()}> <Spinner /> @@ -552,8 +553,8 @@ export function SessionTurn( <Match when={props.stepsExpanded}>{i18n.t("ui.sessionTurn.steps.hide")}</Match> <Match when={!props.stepsExpanded}>{i18n.t("ui.sessionTurn.steps.show")}</Match> </Switch> - <span>·</span> - <span>{store.duration}</span> + <span aria-hidden="true">·</span> + <span aria-live="off">{store.duration}</span> <Show when={assistantMessages().length > 0}> <Icon name="chevron-grabber-vertical" size="small" /> </Show> @@ -563,7 +564,7 @@ export function SessionTurn( </div> {/* Response */} <Show when={props.stepsExpanded && assistantMessages().length > 0}> - <div data-slot="session-turn-collapsible-content-inner"> + <div data-slot="session-turn-collapsible-content-inner" aria-hidden={working()}> <For each={assistantMessages()}> {(assistantMessage) => ( <AssistantMessageItem @@ -589,6 +590,9 @@ export function SessionTurn( </div> </Show> {/* Response */} + <div class="sr-only" aria-live="polite"> + {!working() && response() ? response() : ""} + </div> <Show when={!working() && (response() || hasDiffs())}> <div data-slot="session-turn-summary-section"> <div data-slot="session-turn-summary-header"> diff --git a/packages/ui/src/components/text-field.tsx b/packages/ui/src/components/text-field.tsx index a4eafa561..0694e421e 100644 --- a/packages/ui/src/components/text-field.tsx +++ b/packages/ui/src/components/text-field.tsx @@ -103,6 +103,7 @@ export function TextField(props: TextFieldProps) { variant="ghost" onClick={handleCopy} data-slot="input-copy-button" + aria-label={copied() ? i18n.t("ui.textField.copied") : i18n.t("ui.textField.copyToClipboard")} /> </Tooltip> </Show> diff --git a/packages/ui/src/components/toast.tsx b/packages/ui/src/components/toast.tsx index f34c46d42..9493e50ca 100644 --- a/packages/ui/src/components/toast.tsx +++ b/packages/ui/src/components/toast.tsx @@ -62,7 +62,7 @@ function ToastActions(props: ComponentProps<"div">) { } function ToastCloseButton(props: ToastCloseButtonProps & ComponentProps<"button">) { - return <Kobalte.CloseButton data-slot="toast-close-button" as={IconButton} icon="close" variant="ghost" {...props} /> + return <Kobalte.CloseButton data-slot="toast-close-button" as={IconButton} icon="close" variant="ghost" aria-label="Dismiss" {...props} /> } function ToastProgressTrack(props: ComponentProps<typeof Kobalte.ProgressTrack>) { |
