// Copyright (c) Meta Platforms, Inc. and affiliates. /** * @file VisuallyHidden.tsx * @input Uses React createElement/ElementType, stylex * @output Exports VisuallyHidden component and VisuallyHiddenProps * @position Accessibility primitive; renders content in the a11y tree while * hiding it visually (icon-only labels, aria-live regions, SR-only context) * * SYNC: When modified, update these files to stay in sync: * - /packages/core/src/VisuallyHidden/VisuallyHidden.doc.mjs * - /packages/core/src/VisuallyHidden/VisuallyHidden.test.tsx * - /apps/storybook/stories/VisuallyHidden.stories.tsx * - /packages/cli/assets/templates/blocks/components/VisuallyHidden/ (showcase blocks) */ import { createElement, type ElementType, type ReactNode, type Ref } from 'react'; import type { BaseProps } from "../BaseProps.js"; import * as stylex from "../style-runtime.js"; /** * VisuallyHidden is deliberately styling-free: it exists to *not* be seen, so it * intentionally omits `xstyle`/`className`/`style`. The clip block is fixed and * non-overridable — styling a visually-hidden node is always a mistake. The * accessibility pass-throughs from `BaseProps` (`aria-*`, `role`, `id`, * `data-*`, event handlers) remain, since the live-region use case needs them. */ export interface VisuallyHiddenProps extends Omit, 'className' | 'style'> { /** Ref forwarded to the rendered element. */ ref?: React.Ref; /** Content to expose to assistive technology while hidden from sight. */ children: ReactNode; /** * HTML tag to render as. Defaults to `'span'` (inline) for the common * icon-label case; pass a block element such as `'div'` when wrapping block * content or hosting an `aria-live` region. This is a structural choice, not * a visual one. * * @default 'span' */ as?: ElementType; } /** * Renders its children in the accessibility tree while hiding them visually. * * Use for content that assistive technology must perceive but sighted users * should not see: accessible names for icon-only controls, `aria-live` * announcement regions, and supplementary screen-reader context. * * @example * ``` * * Delete incident * * * {`Moved ${task} to ${column}`} * * ``` */ export function VisuallyHidden({ children, as: element = 'span', ref, ...props }: VisuallyHiddenProps) { return createElement(element, { ref: ref as Ref, // Spread consumer props (aria-*, role, id, data-*, handlers) first, then // the fixed clip styles, so the visually-hidden class can never be // displaced. There is no consumer className/style to merge — by design. ...props, ...{ className: "x10l6tqk x1i1rx1s xjm9jq1 xkdpibf x1717udv xb3r6kr xzpqnlu xuxw1ft xng3xce x13vifvy x1o0tod x47corl x87ps6o" } }, children); } VisuallyHidden.displayName = 'VisuallyHidden';