---
title: "Getting Started with a11yShiny"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Getting Started with a11yShiny}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  eval = FALSE
)
```

## Overview

`a11yShiny` provides accessible drop-in replacements for popular Shiny UI
functions. Every component enforces ARIA attributes, visible labels, and
semantic HTML according to [BITV 2.0](https://bitvtest.de/) (the German
implementation of WCAG 2.1).

The package covers four areas:

1. **Page layout** -- `a11y_fluidPage`, `a11y_fluidRow`, `a11y_column`
2. **Input controls** -- `a11y_actionButton`, `a11y_selectInput`,
   `a11y_numericInput`, `a11y_textInput`, `a11y_radioButtons`,
   `a11y_dateInput`
3. **Composite widgets** -- `a11y_textButtonGroup`, `a11y_textInputsGroup`,
   `a11y_highContrastButton`
4. **Data display** -- `a11y_renderDataTable`, `a11y_ggplot2_line`,
   `a11y_ggplot2_bar`

## Installation

```{r install}
# Install from source
devtools::install()
```

## Accessible Page Layout

`a11y_fluidPage` wraps `shiny::fluidPage` and enforces two accessibility
essentials: a page **title** and a **language** attribute on the `<html>`
element. It also creates proper landmark regions (`<main>`, `<header>`,
`<nav>`, `<footer>`, `<aside>`).

```{r layout}
library(shiny)
library(a11yShiny)

ui <- a11y_fluidPage(
  title = "My Accessible App",
  lang = "en",
  header = tags$header(tags$h1("Dashboard")),
  nav = tags$nav(tags$a(href = "#", "Home")),
  footer = tags$footer("Footer content"),

  # Everything passed via ... goes into <main>
  a11y_fluidRow(
    a11y_column(6, tags$p("Left column")),
    a11y_column(6, tags$p("Right column"))
  )
)
```

`a11y_fluidRow` validates that all direct children are `a11y_column` elements
and that their widths (plus offsets) sum to 12, preventing broken grid
layouts.

## Input Controls

All input wrappers share a common set of accessibility features:

- **Visible label is required.** Omitting it causes an error.
- **`describedby_text`** creates a screen-reader-only description linked
  via `aria-describedby`.
- **`heading_level`** optionally marks the label as a heading
  (`role="heading"` + `aria-level`), useful for sectioned forms.

### Action Button

```{r action-button}
# Button with visible label
a11y_actionButton("go", label = "Submit")

# Icon-only button -- aria_label is required when label is missing
a11y_actionButton(
  "search",
  icon       = icon("search"),
  aria_label = "Search"
)
```

### Select Input

```{r select-input}
a11y_selectInput(
  inputId = "dataset",
  label = "Choose a dataset",
  choices = c("iris", "mtcars", "faithful"),
  describedby_text = "Select one of the built-in R datasets."
)
```

### Numeric Input

```{r numeric-input}
a11y_numericInput(
  inputId = "n",
  label   = "Number of observations",
  value   = 100,
  min     = 1,
  max     = 1000,
  step    = 10
)
```

### Text Input

```{r text-input}
a11y_textInput(
  inputId     = "name",
  label       = "Your name",
  placeholder = "e.g. Jane Doe"
)
```

### Radio Buttons

```{r radio-buttons}
a11y_radioButtons(
  inputId = "color",
  label   = "Favourite colour",
  choices = c("Red", "Green", "Blue")
)
```

### Date Input

```{r date-input}
a11y_dateInput(
  inputId  = "start",
  label    = "Start date",
  value    = Sys.Date(),
  language = "en"
)
```

## Composite Widgets

### Text + Button Group

`a11y_textButtonGroup` pairs a text input with an action button. The button
automatically receives `aria-controls` pointing to the text input.

```{r text-button-group}
a11y_textButtonGroup(
  textId             = "query",
  buttonId           = "run_query",
  label              = "Search term",
  placeholder        = "Enter a keyword",
  button_icon        = icon("search"),
  button_aria_label  = "Run search",
  layout             = "inline"
)
```

### Grouped Text Inputs

`a11y_textInputsGroup` wraps multiple related text fields in a
`<fieldset>` / `<legend>` structure, which screen readers announce as a
single group.

```{r text-inputs-group}
a11y_textInputsGroup(
  groupId = "address",
  legend = "Postal address",
  inputs = list(
    list(inputId = "street", label = "Street"),
    list(inputId = "city", label = "City"),
    list(inputId = "zip", label = "ZIP code", width = "120px")
  )
)
```

### High-Contrast Toggle

`a11y_highContrastButton` adds a button that toggles a `.high-contrast`
CSS class on the `<body>` element and manages its own `aria-pressed` state.

```{r contrast-toggle}
a11y_highContrastButton(
  inputId    = "contrast",
  label      = "High Contrast",
  aria_label = "Toggle high-contrast mode"
)
```

## Accessible Data Tables

`a11y_renderDataTable` wraps `DT::renderDataTable`. It enables the
**KeyTable** extension for keyboard navigation by default and warns when
inaccessible features (copy/print/pdf buttons, column filters) are used.

```{r datatable}
server <- function(input, output, session) {
  output$table <- a11y_renderDataTable(
    expr = iris,
    lang = "en"
  )
}
```

German translations are built in -- set `lang = "de"` and the table
interface is fully translated. For other languages, pass a custom list via
`dt_language`.

## Accessible Charts (ggplot2)

Both chart helpers use a high-contrast, WCAG-compliant colour palette by
default. The line chart also distinguishes series by marker shape, so the
information is not conveyed by colour alone.

### Line Chart

```{r line-chart}
df <- data.frame(
  year  = rep(2020:2024, 2),
  value = c(10, 14, 13, 17, 20, 8, 9, 11, 12, 15),
  group = rep(c("A", "B"), each = 5)
)

a11y_ggplot2_line(
  data  = df,
  x     = year,
  y     = value,
  group = group,
  title = "Trend by Group",
  x     = "Year",
  y     = "Value"
)
```

### Bar Chart

```{r bar-chart}
df <- data.frame(
  category = c("Alpha", "Beta", "Gamma"),
  count    = c(23, 17, 31)
)

a11y_ggplot2_bar(
  data  = df,
  x     = category,
  y     = count,
  title = "Counts by Category"
)
```

## Key Design Decisions

- **Fail fast.** Missing labels or invalid configurations stop with a clear
  error message instead of producing inaccessible output silently.
- **Drop-in API.** Every function mirrors the signature of its Shiny
  counterpart and adds only the extra parameters needed for accessibility.
- **No runtime overhead.** ARIA attributes are set at render time in R; the
  shipped JavaScript is minimal (contrast toggle, focus management).

## Demo App Walkthrough

The package ships with a complete Shiny application that places standard
Shiny components **side-by-side** with their accessible `a11y_*`
counterparts. You can launch it with:

```{r run-demo}
shiny::runApp(system.file("examples/demo", package = "a11yShiny"))
```

The rest of this section walks through the demo and highlights **what the
standard component gets wrong** and **how the `a11y_*` wrapper fixes it**.

### Page structure and landmarks

The demo wraps its entire UI in `a11y_fluidPage`, which enforces a page
`title`, a `lang` attribute on the `<html>` element, and semantic landmark
regions:

```{r demo-layout}
ui <- a11y_fluidPage(
  lang = "de",
  title = "Demo",
  header = tags$header(
    class = "page-header",
    tags$h1("Demo Dashboard"),
    tags$h2("A dashboard with a11yShiny components")
  ),
  aside = tags$aside(
    class = "help-panel",
    tags$h2("Help"),
    tags$p("Supplementary information goes here.")
  ),
  footer = tags$footer(tags$p("Copyright 2025")),

  # Everything below becomes <main>
  a11y_fluidRow(
    a11y_column(8, tags$p("Main content")),
    a11y_column(4, a11y_highContrastButton())
  )
)
```

Screen readers use these landmarks (`<header>`, `<main>`, `<aside>`,
`<footer>`) for quick navigation. A standard `fluidPage` produces none of
them.

### Input controls: standard vs. accessible

The demo shows each input type twice -- first the standard Shiny version,
then the accessible wrapper. The key problems with the standard versions
and how `a11yShiny` resolves them are outlined below.

#### Select input

**Standard (inaccessible):** The label is set to `NULL`, so screen readers
cannot announce what the control is for.

```{r demo-select-std}
# Standard -- no visible label, no ARIA description
selectInput("n_breaks", label = NULL, choices = c(10, 20, 35, 50))
```

**Accessible:** A visible label is required (the function errors
otherwise). Optional `heading_level` promotes the label to a heading for
form sections, and `describedby_text` adds a screen-reader-only
description.

```{r demo-select-a11y}
a11y_selectInput(
  inputId = "n_breaks_1",
  label = "Number of bins",
  choices = c(10, 20, 35, 50),
  selected = 20,
  heading_level = 3
)

a11y_selectInput(
  inputId          = "n_breaks_2",
  label            = "Number of bins",
  choices          = c(10, 20, 35, 50),
  selected         = 20,
  describedby_text = "Select the number of histogram bins."
)
```

#### Numeric input

**Standard (inaccessible):** Again `label = NULL`, and no ARIA role or
value attributes on the `<input>` element.

```{r demo-numeric-std}
numericInput("seed", label = NULL, value = 123)
```

**Accessible:** Adds `role="spinbutton"`, `aria-valuemin`, `aria-valuemax`,
and `aria-valuenow` on the control. The `describedby` parameter can link to
an **existing** help-text element by its ID instead of creating a new one.

```{r demo-numeric-a11y}
# With auto-generated sr-only description
a11y_numericInput(
  inputId          = "seed_3",
  label            = "Seed",
  value            = 123,
  heading_level    = 6,
  describedby_text = "Choose the seed for the random number generator."
)

# Linking to an existing help-text element
a11y_numericInput(
  inputId = "seed_1",
  label = "Seed",
  value = 123,
  describedby = "seed_help"
)
```

#### Date input

**Standard:** `dateInput` provides a label, but the datepicker widget has
no heading-level annotation and no `aria-describedby` support.

```{r demo-date-std}
dateInput("mydate", "Choose a date:")
```

**Accessible:** Adds the `language` parameter for locale-aware rendering
and `heading_level` to integrate the label into the page's heading
hierarchy.

```{r demo-date-a11y}
a11y_dateInput(
  "mydate_acc",
  "Choose a date:",
  language      = "de",
  heading_level = 2
)
```

#### Search bar (text + button composite)

**Standard (inaccessible):** A `textInput` with `label = NULL` and an
`actionButton` with `label = NULL` are placed in a raw `<div>`. There is no
ARIA linkage between the two, and neither element has an accessible name.

```{r demo-search-std}
div(
  textInput("searchbox",
    label = NULL,
    placeholder = "Enter your query:", width = "100%"
  ),
  actionButton("do_search", label = NULL, icon = icon("search"))
)
```

**Accessible:** `a11y_textButtonGroup` enforces a visible label on the text
field, requires `button_aria_label` when the button has no visible text, and
automatically wires `aria-controls` from the button to the text input.

```{r demo-search-a11y}
a11y_textButtonGroup(
  textId            = "text-acc",
  buttonId          = "btn-acc",
  label             = "Enter your query:",
  button_icon       = icon("search"),
  button_aria_label = "Search",
  layout            = "inline"
)
```

#### Address form (grouped text inputs)

**Standard (inaccessible):** Four separate `textInput` fields under a
plain `<h3>` heading. Screen readers see no relationship between the
fields.

```{r demo-address-std}
div(h3("Address"))
textInput("adr_street", "Street and number")
textInput("adr_postcode", "ZIP code")
textInput("adr_city", "City")
textInput("adr_country", "Country")
```

**Accessible:** `a11y_textInputsGroup` wraps the fields in a
`<fieldset>` / `<legend>` structure with `role="group"` and
`aria-labelledby`. The legend can be promoted to a heading via
`legend_heading_level`, and a group-level `describedby_text` adds a
screen-reader description for the entire group.

```{r demo-address-a11y}
a11y_textInputsGroup(
  groupId = "address_group",
  legend = "Address",
  inputs = list(
    list(inputId = "adr_street_acc", label = "Street and number"),
    list(inputId = "adr_postcode_acc", label = "ZIP code"),
    list(inputId = "adr_city_acc", label = "City"),
    list(inputId = "adr_country_acc", label = "Country")
  ),
  describedby_text = "Please enter your full postal address.",
  legend_heading_level = 3
)
```

#### Action buttons

**Standard (inaccessible):** Icon-only buttons with `label = NULL` produce
a `<button>` with no accessible name at all.

```{r demo-buttons-std}
# Icon-only button -- no accessible name
actionButton("refresh", label = NULL, icon = icon("refresh"))

# Empty button -- no label, no icon, no aria-label
actionButton("refresh_0", label = NULL)
```

**Accessible:** `a11y_actionButton` requires either a visible `label` or an
`aria_label`. Both can be combined to provide a richer screen-reader
announcement (e.g., a short visible label plus a longer `aria_label`).

```{r demo-buttons-a11y}
# Visible label + icon
a11y_actionButton("refresh_1",
  label = "Refresh",
  icon = icon("refresh")
)

# Icon-only with aria_label
a11y_actionButton("refresh_2",
  icon = icon("refresh"),
  aria_label = "Click to refresh"
)

# Both visible label and aria_label
a11y_actionButton("refresh_3",
  label = "Refresh",
  aria_label = "Click to refresh data"
)
```

### Charts: colour alone is not enough

The demo renders the same data as both a standard `ggplot2` chart and an
accessible version. The standard chart uses a manually chosen palette
(`#A8A8A8`, `#FEF843`, `#6E787F`) whose contrast ratios are too low,
particularly against a white background. The colours are also the **only**
distinguishing feature between series.

```{r demo-chart-std}
# Standard -- insufficient contrast, no shape distinction
ggplot(df, aes(x = time, y = value, color = group)) +
  geom_line() +
  geom_point() +
  scale_color_manual(
    values = c("A" = "#A8A8A8", "B" = "#FEF843", "C" = "#6E787F")
  ) +
  theme_minimal()
```

**Accessible:** `a11y_ggplot2_line` applies a WCAG-compliant palette
(minimum 3:1 contrast ratio to the background) **and** maps each series to
a different marker shape, so colour is never the sole information carrier.
The result is a standard `ggplot2` object that can be extended with
additional layers.

```{r demo-chart-a11y}
p <- a11y_ggplot2_line(
  data = df,
  x = time,
  y = value,
  group = group,
  legend_title = "Group",
  title = "Simulated time series by group"
)

# The result is a regular ggplot2 object -- add layers as usual
p <- p +
  ggplot2::geom_hline(yintercept = 0, linetype = "dashed") +
  ggplot2::labs(x = "Date", y = "Measurement")
```

The same principle applies to `a11y_ggplot2_bar`, which adds black bar
outlines so bars remain distinguishable even without colour perception.

### Data tables: keyboard navigation and language

The demo shows a standard `DT::datatable` with column filters and
copy/print/PDF buttons alongside the accessible version.

**Standard (problems):** Column filters (especially the numeric range
slider) are not keyboard-accessible. The copy, print, and PDF buttons
open modal dialogs or browser tabs that are difficult for screen-reader
and keyboard users to operate.

```{r demo-table-std}
output$tbl <- DT::renderDataTable({
  DT::datatable(
    head(iris[, 1:4], 10),
    filter = "top", selection = "none",
    options = list(
      pageLength = 5,
      dom = "Bfrtip",
      buttons = c("excel", "copy", "csv", "pdf", "print")
    )
  )
})
```

**Accessible:** `a11y_renderDataTable` enables the **KeyTable** extension
by default for full keyboard navigation between cells. It **warns** at
render time when inaccessible features are requested (column filters,
copy/print/PDF buttons). Setting `lang = "de"` activates built-in German
translations for all interface strings.

```{r demo-table-a11y}
output$tbl_acc <- a11y_renderDataTable(
  expr = head(iris[, 1:4], 10),
  lang = "de",
  selection = "none",
  extensions = c("Buttons"),
  options = list(
    pageLength = 5,
    dom        = "Bfrtip",
    buttons    = c("excel", "csv")
  )
)
```

Wrap the output in a `<div class="a11y-dt">` on the UI side to
apply the accessible focus and contrast styles shipped with the package:

```{r demo-table-ui}
div(class = "a11y-dt", dataTableOutput("tbl_acc"))
```

### Running the demo yourself

Use the demo to compare both versions with accessibility testing tools:
inspect the rendered HTML in your browser's developer tools, navigate with
the keyboard only (Tab / Shift-Tab / Arrow keys), or test with a screen
reader such as NVDA, JAWS, or VoiceOver.
