Skip to content

Commit

Permalink
Add visually hidden prefix and suffix text to links and buttons (#465)
Browse files Browse the repository at this point in the history
It would be nice to be able to simply add visually hidden text to links
and buttons without having to manually construct the HTML. It comes in
really handy when working in dense admin interfaces where there might
just be enough space for 'View', but we want to give screenreader users
and idea of _where_ the link will take them.

## Work so far

- Split out `#extract_button_link_args`
- Rework and simplify the link and button helpers
- Add `govuk_visually_hidden` helper which makes creating visually
hidden text easy
- Add keyword arguments `visually_hidden_prefix` and
`visually_hidden_suffix` which injects the visually hidden text before
or after the link or button text

## Example use

With keyword arguments:

```erb
<%= govuk_link_to("Delete", "#", visually_hidden_suffix: "first item")  %>
```

With a block:

```erb
<%= govuk_link_to("#") do %>
  Delete <%= govuk_visually_hidden("first item") %>
<% end %>
```

Both will output:

```html
<a href="#" class="govuk-link">Delete<span class="govuk-visually-hidden"> first item</span></a>
```

Prefixes work in the same way:

```erb
<%= govuk_link_to("Mr Jones", "#", visually_hidden_prefix: "Remove")  %>
```

```html
<a href="#" class="govuk-link"><span class="govuk-visually-hidden">Remove </span>Mr Jones</a>
```
  • Loading branch information
peteryates authored Nov 28, 2023
2 parents e8a11a7 + aa77c40 commit f01217d
Show file tree
Hide file tree
Showing 10 changed files with 281 additions and 34 deletions.
79 changes: 45 additions & 34 deletions app/helpers/govuk_link_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,32 @@
module GovukLinkHelper
using HTMLAttributesUtils

def govuk_link_to(name, href = nil, new_tab: false, inverse: false, muted: false, no_underline: false, no_visited_state: false, text_colour: false, **kwargs, &block)
def govuk_link_to(name, href = nil, new_tab: false, inverse: false, muted: false, no_underline: false, no_visited_state: false, text_colour: false, visually_hidden_prefix: nil, visually_hidden_suffix: nil, **kwargs, &block)
link_args = extract_link_args(new_tab: new_tab, inverse: inverse, muted: muted, no_underline: no_underline, no_visited_state: no_visited_state, text_colour: text_colour, **kwargs)
link_text = build_text(name, visually_hidden_prefix: visually_hidden_prefix, visually_hidden_suffix: visually_hidden_suffix, &block)

if block_given?
link_to(block.call, href, **link_args)
else
link_to(name, href, **link_args)
end
link_to(link_text, href, **link_args)
end

def govuk_mail_to(email_address, name = nil, new_tab: false, inverse: false, muted: false, no_underline: false, no_visited_state: false, text_colour: false, **kwargs, &block)
def govuk_mail_to(email_address, name = nil, new_tab: false, inverse: false, muted: false, no_underline: false, no_visited_state: false, text_colour: false, visually_hidden_prefix: nil, visually_hidden_suffix: nil, **kwargs, &block)
link_args = extract_link_args(new_tab: new_tab, inverse: inverse, muted: muted, no_underline: no_underline, no_visited_state: no_visited_state, text_colour: text_colour, **kwargs)
link_text = build_text(name, visually_hidden_prefix: visually_hidden_prefix, visually_hidden_suffix: visually_hidden_suffix, &block)

if block_given?
mail_to(email_address, block.call, **link_args)
else
mail_to(email_address, name, **link_args)
end
mail_to(email_address, link_text, **link_args)
end

def govuk_button_to(name, href = nil, disabled: false, inverse: false, secondary: false, warning: false, **kwargs, &block)
def govuk_button_to(name, href = nil, disabled: false, inverse: false, secondary: false, warning: false, visually_hidden_prefix: nil, visually_hidden_suffix: nil, **kwargs, &block)
button_args = extract_button_args(new_tab: false, disabled: disabled, inverse: inverse, secondary: secondary, warning: warning, **kwargs)
button_text = build_text(name, visually_hidden_prefix: visually_hidden_prefix, visually_hidden_suffix: visually_hidden_suffix, &block)

if block_given?
button_to(block.call, href, **button_args)
else
button_to(name, href, **button_args)
end
button_to(button_text, href, **button_args)
end

def govuk_button_link_to(name, href = nil, new_tab: false, disabled: false, inverse: false, secondary: false, warning: false, **kwargs, &block)
button_args = extract_button_args(new_tab: new_tab, disabled: disabled, inverse: inverse, secondary: secondary, warning: warning, **kwargs)
def govuk_button_link_to(name, href = nil, new_tab: false, disabled: false, inverse: false, secondary: false, warning: false, visually_hidden_prefix: nil, visually_hidden_suffix: nil, **kwargs, &block)
button_args = extract_button_link_args(new_tab: new_tab, disabled: disabled, inverse: inverse, secondary: secondary, warning: warning, **kwargs)
button_text = build_text(name, visually_hidden_prefix: visually_hidden_prefix, visually_hidden_suffix: visually_hidden_suffix, &block)

if block_given?
link_to(block.call, href, **button_args)
else
link_to(name, href, **button_args)
end
link_to(button_text, href, **button_args)
end

def govuk_breadcrumb_link_to(name, href = nil, **kwargs, &block)
Expand Down Expand Up @@ -92,33 +80,56 @@ def button_attributes(disabled)
end

def extract_link_args(new_tab: false, inverse: false, muted: false, no_underline: false, no_visited_state: false, text_colour: false, **kwargs)
link_classes = extract_link_classes(inverse: inverse, muted: muted, no_underline: no_underline, no_visited_state: no_visited_state, text_colour: text_colour)

{ **link_classes, **new_tab_args(new_tab) }.deep_merge_html_attributes(kwargs)
end

def extract_button_link_args(new_tab: false, disabled: false, inverse: false, secondary: false, warning: false, **kwargs)
button_classes = extract_button_classes(inverse: inverse, secondary: secondary, warning: warning)

{ **button_classes, **button_attributes(disabled), **new_tab_args(new_tab) }.deep_merge_html_attributes(kwargs)
end

def extract_button_args(disabled: false, inverse: false, secondary: false, warning: false, **kwargs)
button_classes = extract_button_classes(inverse: inverse, secondary: secondary, warning: warning)

{ **button_classes, **button_attributes(disabled) }.deep_merge_html_attributes(kwargs)
end

def extract_link_classes(inverse: false, muted: false, no_underline: false, no_visited_state: false, text_colour: false)
{
class: govuk_link_classes(
inverse: inverse,
muted: muted,
no_underline: no_underline,
no_visited_state: no_visited_state,
text_colour: text_colour
),
**new_tab_args(new_tab)
}.deep_merge_html_attributes(kwargs)
text_colour: text_colour,
)
}
end

def extract_button_args(new_tab: false, disabled: false, inverse: false, secondary: false, warning: false, **kwargs)
def extract_button_classes(inverse: false, secondary: false, warning: false)
{
class: govuk_button_classes(
inverse: inverse,
secondary: secondary,
warning: warning
),
**button_attributes(disabled),
**new_tab_args(new_tab)
}.deep_merge_html_attributes(kwargs)
)
}
end

def brand
Govuk::Components.brand
end

def build_text(original, visually_hidden_prefix:, visually_hidden_suffix:, &block)
prefix = (visually_hidden_prefix.present?) ? visually_hidden_prefix + " " : nil
text = (block_given?) ? block.call : original
suffix = (visually_hidden_suffix.present?) ? " " + visually_hidden_suffix : nil

safe_join([govuk_visually_hidden(prefix), text, govuk_visually_hidden(suffix)].compact)
end
end

ActiveSupport.on_load(:action_view) { include GovukLinkHelper }
13 changes: 13 additions & 0 deletions app/helpers/govuk_visually_hidden_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module GovukVisuallyHiddenHelper
def govuk_visually_hidden(text = nil, focusable: false, &block)
content = (block_given?) ? block.call : text

return if content.blank?

visually_hidden_class = focusable ? "govuk-visually-hidden-focusable" : "govuk-visually-hidden"

tag.span(content, class: visually_hidden_class)
end
end

ActiveSupport.on_load(:action_view) { include GovukSkipLinkHelper }
17 changes: 17 additions & 0 deletions guide/content/helpers/link.slim
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,23 @@ markdown:
caption: "Other link styles",
code: govuk_link_other_styles)

== render('/partials/example.*',
caption: "Links with visually hidden text",
code: govuk_link_with_visually_hidden_text) do

markdown:
When space is limited we sometimes rely on the link's position to provide
context about its target. An example of this is in admin interfaces where rows
in a table often have a 'View' link, or a 'Delete' button.

Omitting the extra text entirely leaves users of assistive technologies like
screen readers at a disadvantage, as they can't infer that context. We can
solve this problem using visually hidden text that isn't visible on the screen
but will be read out by screen readers.

The keyword arguments automatically add a space between the visually
hidden and regular text.

hr.govuk-section-break.govuk-section-break--xl.govuk-section-break--visible

== render('/partials/example.*',
Expand Down
25 changes: 25 additions & 0 deletions guide/content/helpers/visually-hidden-text.slim
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
title: Visually hidden text
---

markdown:
Most of the time content should be both visible on the screen and availble to
screen readers. However, somtimes when space is at a premium, we may want
to hide text that would clutter the screen. This puts users of assistive
technology like screen readers at a disadvantage.

We can hide content but make it available to users of assistive technology
using the `govuk-visually-hidden` class. This library provides a helper method
which makes hiding content easier.

== render('/partials/example.*',
caption: "Setting visually hidden text with an argument",
code: visually_hidden_text_via_argument)

== render('/partials/example.*',
caption: "Setting visually hidden text with a block",
code: visually_hidden_text_via_block)

== render('/partials/example.*',
caption: "Focusable visually hidden text",
code: focusable_visually_hidden_text)
1 change: 1 addition & 0 deletions guide/layouts/partials/links.slim
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ section#links.govuk-width-container
li== govuk_link_to('Skip link', '/helpers/skip-link')
li== govuk_link_to('Back to top link', '/helpers/back-to-top-link')
li== govuk_link_to('Title with error prefix', '/helpers/title-with-error-prefix')
li== govuk_link_to('Visually hidden text', '/helpers/visually-hidden-text')

.govuk-grid-column-two-thirds
h2.govuk-heading-m Components
Expand Down
6 changes: 6 additions & 0 deletions guide/lib/examples/link_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ def govuk_button_inverse
BUTTON
end

def govuk_link_with_visually_hidden_text
<<~VISUALLY_HIDDEN_LINK
= govuk_link_to('View', '#', visually_hidden_suffix: 'account')
VISUALLY_HIDDEN_LINK
end

def govuk_button_other_styles
<<~BUTTONS
.govuk-button-group
Expand Down
37 changes: 37 additions & 0 deletions guide/lib/examples/visually_hidden_helpers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module Examples
module VisuallyHiddenHelpers
def visually_hidden_text_via_argument
<<~SNIPPET
p Regular text
= govuk_visually_hidden("This content is visually hidden")
p More regular text
SNIPPET
end

def visually_hidden_text_via_block
<<~SNIPPET
p Regular text
= govuk_visually_hidden do
p This paragraph is visually hidden
p More regular text
SNIPPET
end

def focusable_visually_hidden_text
<<~SNIPPET
p Regular text
p
a href="#"
| Some link
= govuk_visually_hidden("Focus on me", focusable: true)
p More regular text
SNIPPET
end
end
end
4 changes: 4 additions & 0 deletions guide/lib/helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ class Application < Rails::Application; end
require 'components/govuk_component/warning_text_component'

require 'helpers/govuk_link_helper'
require 'helpers/govuk_visually_hidden_helper'

use_helper GovukVisuallyHiddenHelper
use_helper GovukLinkHelper
use_helper GovukComponentsHelper
use_helper Examples::LinkHelpers
Expand Down Expand Up @@ -115,5 +117,7 @@ class Application < Rails::Application; end
use_helper Examples::CommonOptionsHelpers
use_helper Examples::BackToTopLinkHelpers
use_helper Examples::TitleWithErrorPrefixHelpers
use_helper Examples::VisuallyHiddenHelpers

ActiveSupport.on_load(:action_view) { include GovukVisuallyHiddenHelper }
ActiveSupport.on_load(:action_view) { include GovukLinkHelper }
Loading

0 comments on commit f01217d

Please sign in to comment.