# a11yShiny

<!-- badges: start -->

[![EUPL-1.2](https://img.shields.io/badge/license-EUPL--1.2-blue)](https://interoperable-europe.ec.europa.eu/collection/eupl/eupl-text-eupl-12)
[![R version](https://img.shields.io/badge/R-%E2%89%A5%204.1-blue)](https://cran.r-project.org/)

<!-- badges: end -->

Accessible wrappers for popular R [Shiny](https://shiny.posit.co/) UI components, enforcing ARIA attributes and structural requirements in line with [BITV 2.0](https://www.barrierefreiheit-dienstekonsolidierung.bund.de/Webs/PB/DE/gesetze-und-richtlinien/bitv2-0/bitv2-0-node.html) and WCAG 2.1 AA.

## Installation

Install the package directly from GitLab:

```r
install.packages("devtools")
devtools::install_git("https://gitlab.opencode.de/bmbf/datenlabor/barrierefrei-r.git")
```

Alternatively, clone the repository and use [renv](https://rstudio.github.io/renv/) for reproducible dependency management:

```r
# Set your working directory to the package root
setwd("path/to/barrierefrei-r")

renv::init()
renv::install(".")
renv::snapshot()
```

## Getting Started

Load the package and replace standard Shiny UI functions with their accessible counterparts:

```r
library(a11yShiny)

ui <- a11y_fluidPage(
  lang = "de",
  title = "Accessible Demo",
  a11y_fluidRow(
    a11y_column(6, a11y_actionButton("go", label = "Go!")),
    a11y_column(6, a11y_selectInput("sel", label = "Choose", choices = 1:3))
  )
)
```

For accessible DataTables and basic line/bar charts:

```r
output$tbl <- a11y_renderDataTable({ head(iris, 10) }, lang = "de")

output$plt <- renderPlotly({
  p <- a11y_ggplot2_line(data = iris, x = ~Species, y = ~Sepal.Length)
  plotly::ggplotly(p)
})
```

A full demo app is included in the package:

```r
shiny::runApp(system.file("examples", "demo", "app.R", package = "a11yShiny"))
```

For detailed documentation on each function, see the package reference (`?a11y_fluidPage`, `?a11y_actionButton`, etc.) and the examples in `inst/examples/`.

## Core Functions & Accessibility Features

### Layout & Structure

#### **a11y_fluidPage, a11y_fluidRow, a11y_column:**

Wrappers for fluidPage(), fluidRow() and column().

- Sets and validates `<title>` title, `<lang>` language, and unique `<main>` landmark.
- Ensures flexible layout with classes `.a11y-flow`, `.a11y-row`, `.a11y-col` and optional header/nav/footer.
- Ensures that only a11y_columns are used within a11y_fluidRow() (not the standard shiny `column()` function).
- Validates sum of column widths and offset (12) and single column width and offset (1–12) within a11y_fluidRow() (only for direct children columns).
- Warnings if nested fluidRows are detected because it can potentially create problems for keyboard users.
- Optional aria-labels can be added.
- **BITV 2.0 Criteria:** _9.2.4.2, 9.3.1.1, 9.2.4.1, 9.1.4.10, 9.1.3.4, 9.1.4.4_
- **Limitations:** Heading levels/order (`<h1>`, `<h2>`, ...) is not enforced. No validation of meanigful (ARIA) text label (_9.2.4.6_). If nested fluidRows are used seamless keyboard navigation is not guaranteed (_9.2.1.1_)

---

### Inputs

#### **a11y_actionButton**

Wrapper for actionButton().

- Sets and validates visible label or non-visible aria-label.
- Sets and validates aria-label for icon-only buttons for screenreader users.
- Ensures clear contrast and focus styling with class `.a11y-btn`.
- **BITV 2.0 Criteria:** _9.1.1.1a, 9.1.4.1, 9.1.4.11, 9.1.4.3, 9.2.1.1, 9.2.4.7, 9.2.5.3, 9.4.1.2_
- **Limitations:** No validation of meanigful text label (_9.2.4.6_).

#### **a11y_selectInput(), a11y_numericInput(), a11y_textInput, a11y_dateInput, a11y_radioButtons**

Wrapper for selectInput(), numericInput(), textInput(), dateInput() and radioButtons().

- Sets and validates visible label.
- Optional headline marking (aria-level) can be added (1-6 for `<h1>`-`<h6>`).
- Optional helping description (aria-describedby) can be added - either reference an existing text (via ID) or add a non-visible text which only Screenreaders can read (via class `.a11y-sr-only`).
- Ensures clear contrast and focus styling with classes `.a11y-select`, `.a11y-numeric`, `.a11y-text`, `.a11y-date` and `.a11y-radio`.
- **BITV 2.0 Criteria:** _9.3.3.2, 9.1.3.1a, 9.1.3.1h, 9.1.4.1, 9.1.3.2, 9.1.4.3, 9.1.4.11, 9.1.4.13, 9.2.1.1, 9.2.4.7, 9.2.5.3, 9.4.1.2_
- **Limitations:** No validation of correct headline order (_9.1.3.1a_), meanigful text label (_9.2.4.6_). Semantic groupings of combined input elements is not covered.

#### **a11y_textButtonGroup()**

Composite wrapper combining a labeled text input and an action button (e. g. for a search bar with button).

- Internally uses `a11y_textInput()` and `a11y_actionButton()` with their validations and CSS focus styling.
- Adds `aria-controls` to the button, by default pointing to the text input ID (configurable), so assistive technologies understand which control is affected by the button.
- Optional helping description (aria-describedby) can be added - either reference an existing text (via ID) or add a non-visible text which only Screenreaders can read (via class `.a11y-sr-only`).
- Supports different layout modes (e.g. inline vs. stacked) via `.a11y-text-action-group` and modifier classes.
- Each inner element has its own ID and can be addressed separately in server logic and UI.
- **BITV 2.0 Criteria:** _9.1.1.1a, 9.1.3.1a, 9.1.3.1h, 9.1.3.2, 9.1.4.1, 9.1.4.3, 9.1.4.11, 9.2.1.1, 9.2.4.7, 9.2.5.3, 9.3.3.2, 9.4.1.2_
- **Limitations:** No automatic validation of semantic button text or the precise semantics of aria-controls (_9.2.4.6_). Correct wiring between button and controlled element remains responsibility of the user.

#### **a11y_textInputsGroup()**

Wrapper for a group of related textInput elements inside a `<fieldset>` with `<legend>` (e.g. address fields, date components, grouped inputs).

- Renders a `<fieldset>` with a visible `<legend>` as group label and marks it with `role="group"` and `aria-labelledby` to the legend.
- Each inner text input:
  - Sets and validates visible label.
  - Optional headline marking (aria-level) can be added (1-6 for `<h1>`-`<h6>`).
  - Optional helping description (aria-describedby) can be added - either reference an existing text (via ID) or add a non-visible text which only Screenreaders can read (via class `.a11y-sr-only`).
  - Ensures clear contrast and focus styling with class `.a11y-text`.
- **BITV 2.0 Criteria:** _9.1.3.1a, 9.1.3.1h, 9.1.3.2, 9.1.4.1, 9.1.4.3, 9.2.1.1, 9.2.4.7, 9.2.5.3, 9.3.3.2, 9.4.1.2_
- **Limitations:** No validation of the semantic correctness of group labels or titles; it only enforces that an accessible name is present. Logical grouping and content semantics must be chosen by the user.

---

### Tables

#### **a11y_renderDataTable()**

Wrapper for renderDataTable().

- Sets and validates language (supports German and English by default).
- Optional language can be set (other than de/en) via dt_language file internationalization.
- Warnings if non-accessible "Copy", "Pdf" and "Print" export functions are used.
- Warnings if non-accessible dataTable filter in numeric columns are used.
- Sets KeyTab keyboard navigation as default.
- Optional clear contrast and focus styling with the class `.a11y-dt`: To enable CSS style, wrap dataTableOutput() in a div with a custom class, e.g.:

```r
div(class = "a11y-dt", DT::dataTableOutput("mytable"))
```

- **BITV 2.0 Criteria:** _9.3.1.2, 9.1.3.1e, 9.2.4.7, 9.1.4.3, 9.1.3.4, 9.1.4.4_
- **Limitations:** DataTable can adapt to different screen sizes (smaller displays, mobile) only via horizontal scrolling (_9.1.4.10_). Setting a language attribute (lang) or other ARIA attributes around the DataTable is not reliably possible using a wrapper because after rendering the JavaScript/widget binding replaces or overwrites some attributes and may reset or remove classes/IDs/ARIA attributes - it must be set individually by the user if it differs from the language in the body (_9.3.1.2_).

---

### Charts

A generic wrapper for ggplot2/ggplotly charts is **not recommended** due to low flexibility and high risk of errors.
Several approaches for accessible diagram wrappers were evaluated, but due to limitations, only the first option was implemented in this package as a minimal example:

1. **Minimal ggplot2 Wrapper per Chart Type** which can be manipulated afterwards (see `a11y_ggplot2_bar/line`).
   **Limitation:** Must be created for every chart type; user modifications after the wrapper can impair accessibility.
2. **Full Chart Wrapper with Internal ggplotly Conversion** which create a complete interactive chart.
   **Limitation:** Most accessible, but least flexible -- only practical for fixed chart formats.
3. **Generic ggplotly Wrapper** which patches accessibility features into a ggplot2 object after creation.
   **Limitation:** Messy and error-prone; post-hoc changes often fail as ggplotly may not reliably map all properties.
4. **A wrapper for plotlyOutput** which adds landmarks and ARIA attributes (aria-describedby).
   **Limitation:** plotlyOutput first renders a simple DIV placeholder in the UI, but after rendering the JavaScript/widget binding replaces or overwrites some attributes and may reset or remove classes/IDs/ARIA attributes; the attribute is not always guaranteed to be visible in the finished DOM tree. It must be set individually by the user.

Support for pattern fills, certain ARIA attributes, or colorblind-friendly mapping is always limited by ggplot2/plotly's underlying capabilities and design. Instead users should follow accessibility guidelines for charts.
**Best practice:**
Users should

- use high-contrast palettes, distinct markers for different groups/lines, outlines for bars, clear labeling,
- add additional labels, descriptions, and helper texts accessible for keyboards and screenreaders.

#### **a11y_ggplot2_line() / a11y_ggplot2_bar()**

Minimal wrapper for ggplot2() for a multi-line and simple bar chart.

- Pre-set accessible color palette (3:1 contrast to white) and warning if user overwrites color palette.
- Pre-set markers (line) and black outline (bar).
- Optional setting of legend title (which prevents legend title duplication due to marker and color legend).
- Wrapper can be flexibly adjusted with ggplot2 (see example in `inst/examples/demo/app.R`).
- **BITV 2.0 Criteria:** _9.1.4.1, 9.1.4.11_
- **Limitations:**
  - ARIA/landmark features for charts must be set via JS or on container level.
  - Minimal wrapper for each chart type needed; modifying chart after wrapper may impair accessibility.
  - No wrapper can guarantee full accessibility once plots are heavily adapted post-hoc.

---

### High-contrast mode

Wrapper for a global high-contrast mode toggle button.

- Renders a clearly emphasized high-contrast button with its **own strong contrast style**, independent of whether high-contrast mode is active.
- On click it toggles the `high-contrast` class on the `<body>` element: All standard Shiny buttons, form controls and dataTable controls switch to a high-contrast style (inverted colors, clear borders and focus outline)
- Sets and updates `aria-pressed="true"/"false"` on the button so screen readers can understand the toggle state.
- Internally uses `a11y_actionButton()` and inherits its validation.
- Receives an additional class `.a11y-high-contrast-toggle` that the JS and CSS high-contrast styling are attached to.
- **BITV 2.0 Criteria:** _9.1.4.1, 9.1.4.3, 9.1.4.11, 9.2.4.7_
- **Limitations:** High-contrast mode is applied globally via a CSS class on <body>. Components with heavily customized styles may need additional adjustments to ensure sufficient contrast and visible focus states in high-contrast mode.

---

### Utility Functions

- **add_aria, add_aria_inside, find_html_tags, find_direct_children_without_class, is_nonblank_string**
  - Utility functions to add ARIA attributes and search tags for custom accessibility patterns.

## Known Limitations

Limitations are mainly library-dependent. Some specific capabilities (modal copy dialogs, responsive DT layouts, numeric DT filters, chart tooltips) cannot yet be made fully accessible through wrappers alone. Always test your outputs with accessibility auditing tools (see below).

## Getting Help

- For bug reports, please use the [issue tracker](https://gitlab.opencode.de/bmbf/datenlabor/barrierefrei-r/-/issues).
- For accessibility testing, the following tools are recommended:
  - [BITV Test Toolkit](https://bitvtest.de/test-methodik/web/werkzeugliste#tablesbm)
  - [WAVE Evaluation Tool](https://wave.webaim.org/)
  - [Firefox Web Developer Add-on](https://addons.mozilla.org/de/firefox/addon/web-developer/)
  - [Chrome Lighthouse](https://chromewebstore.google.com/detail/lighthouse/blipmdconlkpinefehnmjammfjpmpbjk?hl=de)

## Contributing

Contributions to **a11yShiny** are welcome. Please open an issue or submit a merge request on the [GitLab repository](https://gitlab.opencode.de/bmbf/datenlabor/barrierefrei-r).

## License

a11yShiny is licensed under the European Union Public Licence (EUPL) v1.2.

## R Version Support

a11yShiny requires R >= 4.1.
