Skip to main content
The HiddenVisually component hides content from visual display while keeping it accessible to assistive technologies like screen readers. This is essential for providing context that sighted users can infer visually.

Basic Usage

import { HiddenVisually, Button } from 'reshaped';
import { Search } from './icons';

function Example() {
  return (
    <Button icon={Search}>
      <HiddenVisually>Search</HiddenVisually>
    </Button>
  );
}
import { HiddenVisually, Actionable } from 'reshaped';

function SkipNav() {
  return (
    <HiddenVisually>
      <Actionable href="#main-content">
        Skip to main content
      </Actionable>
    </HiddenVisually>
  );
}

Form Labels

import { HiddenVisually, TextField } from 'reshaped';
import { Search } from './icons';

function SearchField() {
  return (
    <div>
      <HiddenVisually as="label" htmlFor="search">
        Search
      </HiddenVisually>
      <TextField
        id="search"
        placeholder="Search..."
        icon={Search}
      />
    </div>
  );
}

Props

children
React.ReactNode
Node for inserting children - content that should be hidden visually but accessible to screen readers

When to Use

  • Icon-Only Buttons: Provide text alternatives for icon buttons
  • Skip Links: Create skip navigation links for keyboard users
  • Form Labels: Add labels to inputs that have visual context but no explicit label
  • Loading States: Announce loading states to screen readers
  • Dynamic Content: Announce dynamic content changes
  • Navigation Context: Provide additional context for navigation items

Composition Patterns

Icon Button with Label

import { HiddenVisually, Button } from 'reshaped';
import { Close, Edit, Delete, Share } from './icons';

function IconButton({ icon, label, onClick }) {
  return (
    <Button icon={icon} onClick={onClick}>
      <HiddenVisually>{label}</HiddenVisually>
    </Button>
  );
}

// Usage
<IconButton icon={Close} label="Close dialog" onClick={() => {}} />
<IconButton icon={Edit} label="Edit item" onClick={() => {}} />
<IconButton icon={Delete} label="Delete item" onClick={() => {}} />
<IconButton icon={Share} label="Share" onClick={() => {}} />
import { HiddenVisually, Stack, Actionable } from 'reshaped';

function SkipLinks() {
  return (
    <HiddenVisually>
      <Stack gap={2}>
        <Actionable href="#main-content">
          Skip to main content
        </Actionable>
        <Actionable href="#navigation">
          Skip to navigation
        </Actionable>
        <Actionable href="#footer">
          Skip to footer
        </Actionable>
      </Stack>
    </HiddenVisually>
  );
}

function Layout() {
  return (
    <>
      <SkipLinks />
      <nav id="navigation">{/* ... */}</nav>
      <main id="main-content">{/* ... */}</main>
      <footer id="footer">{/* ... */}</footer>
    </>
  );
}

Loading Announcement

import { HiddenVisually, Spinner, Stack } from 'reshaped';

function LoadingState({ message = "Loading..." }) {
  return (
    <Stack align="center" gap={2}>
      <Spinner />
      <HiddenVisually role="status">
        {message}
      </HiddenVisually>
    </Stack>
  );
}

Data Table Headers

import { HiddenVisually, View, Stack, Text, Button } from 'reshaped';
import { Edit, Delete } from './icons';

function DataTable({ rows }) {
  return (
    <table>
      <thead>
        <tr>
          <th>Name</th>
          <th>Email</th>
          <th>
            <HiddenVisually>Actions</HiddenVisually>
          </th>
        </tr>
      </thead>
      <tbody>
        {rows.map((row) => (
          <tr key={row.id}>
            <td>{row.name}</td>
            <td>{row.email}</td>
            <td>
              <Stack direction="row" gap={1}>
                <Button icon={Edit} size="small">
                  <HiddenVisually>Edit {row.name}</HiddenVisually>
                </Button>
                <Button icon={Delete} size="small" color="critical">
                  <HiddenVisually>Delete {row.name}</HiddenVisually>
                </Button>
              </Stack>
            </td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

Form Field with Visual Context

import { HiddenVisually, TextField, Stack, Text } from 'reshaped';
import { Search, User, Mail } from './icons';

function SearchForm() {
  return (
    <Stack gap={3}>
      <Stack gap={1}>
        <Text variant="title-6">Find Users</Text>
        <HiddenVisually as="label" htmlFor="user-search">
          Search for users by name or email
        </HiddenVisually>
        <TextField
          id="user-search"
          placeholder="Type to search..."
          icon={Search}
        />
      </Stack>
    </Stack>
  );
}

Status Indicators

import { HiddenVisually, View, Text, Stack } from 'reshaped';

function StatusBadge({ status, label }) {
  const colors = {
    online: 'positive',
    offline: 'neutral',
    busy: 'warning',
    error: 'critical'
  };
  
  return (
    <Stack direction="row" gap={2} align="center">
      <View
        width={2}
        height={2}
        borderRadius="full"
        backgroundColor={colors[status]}
      />
      <Text>{label}</Text>
      <HiddenVisually>Status: {status}</HiddenVisually>
    </Stack>
  );
}

// Usage
<StatusBadge status="online" label="John Doe" />
<StatusBadge status="busy" label="Jane Smith" />
import { HiddenVisually, Stack, Actionable, Icon, Text } from 'reshaped';
import { Home, Settings, User, Notifications } from './icons';

function Navigation({ unreadCount }) {
  return (
    <nav>
      <Stack gap={1}>
        <Actionable href="/">
          <Stack direction="row" gap={2} align="center">
            <Icon svg={Home} />
            <Text>Home</Text>
          </Stack>
        </Actionable>
        
        <Actionable href="/notifications">
          <Stack direction="row" gap={2} align="center">
            <Icon svg={Notifications} />
            <Text>Notifications</Text>
            {unreadCount > 0 && (
              <>
                <View
                  padding={1}
                  paddingInline={2}
                  backgroundColor="critical"
                  borderRadius="full"
                >
                  <Text color="white" variant="caption-1">
                    {unreadCount}
                  </Text>
                </View>
                <HiddenVisually>
                  {unreadCount} unread notification{unreadCount !== 1 ? 's' : ''}
                </HiddenVisually>
              </>
            )}
          </Stack>
        </Actionable>
        
        <Actionable href="/settings">
          <Stack direction="row" gap={2} align="center">
            <Icon svg={Settings} />
            <Text>Settings</Text>
          </Stack>
        </Actionable>
      </Stack>
    </nav>
  );
}

Live Region Announcements

import { HiddenVisually } from 'reshaped';
import { useState, useEffect } from 'react';

function LiveAnnouncements({ message }) {
  const [announcement, setAnnouncement] = useState('');
  
  useEffect(() => {
    if (message) {
      setAnnouncement(message);
      // Clear after announcement
      const timer = setTimeout(() => setAnnouncement(''), 1000);
      return () => clearTimeout(timer);
    }
  }, [message]);
  
  return (
    <HiddenVisually role="status" aria-live="polite" aria-atomic="true">
      {announcement}
    </HiddenVisually>
  );
}

// Usage in a form
function FormWithAnnouncements() {
  const [message, setMessage] = useState('');
  const [saved, setSaved] = useState(false);
  
  const handleSave = () => {
    setSaved(true);
    setMessage('Your changes have been saved successfully');
  };
  
  return (
    <div>
      {/* Form fields */}
      <Button onClick={handleSave}>Save</Button>
      <LiveAnnouncements message={message} />
    </div>
  );
}

Build docs developers (and LLMs) love