Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion _scripts/pages/download.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ Promise.all([config, jQuery, openDownloadOverlay]).then(([config, $, openDownloa
$('#download').text(translateDownload)
}
}
$('#choice-buttons').on('click', updateDownloadButton)
$('#choice-buttons button').on('click', updateDownloadButton)
Comment thread
RMcNeely marked this conversation as resolved.
$('#choice-buttons input').on('input', updateDownloadButton)
updateDownloadButton()

Expand Down
85 changes: 76 additions & 9 deletions _scripts/widgets/showcase.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,16 @@ export default class Showcase {
start () {
for (let i = 0; i < this.slides.length; i++) {
const n = this.slides[i]
const $iChoice = $("[href$='" + n + "']", this.container)
const $iChoice = $("[aria-controls='" + n + "']", this.container)
const $iContainer = $('#' + n, this.container)

$iContainer.prepend('<div class="showcase-back"></div>')
$iContainer.prepend('<button type="button" class="showcase-back" aria-controls="showcase-index" aria-expanded="false" aria-label="Back" tabindex="-1"></button>')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why exclude this from the tab index? Shouldn't you want to naturallly focus it instead so that you can get back out of the showcase?

@ellieplayswow ellieplayswow Oct 13, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

by default actually no. we set the tabindex to 0 here: https://github.com/elementary/website/pull/4177/changes#diff-a375a752112dc75ce153214314f0ad86ca13deddee1be26df741aebae00ea20aR133 only for the relevant tab, so once you enter one tab's contents your next action is to go back.

this is technically a bit of a holdover from before i got the hidden working just right on those divs, where since the other tabs were previously visible (just out of your viewport) your next tab target was always the next tab's back button. hope that makes sense, between meetings rn :)

it might be able to be removed now? but it doesn't do any harm being this way i don't think

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure that link is taking me to the right spot in my browser (grrr GH) but should I be focused on this back button when after I hit enter and select a "tab"?


// each choice button
const that = this
$iChoice.on('click', function (e) {
e.preventDefault()
that.slideTo($(this).attr('href').split('#').pop()) // slide on click of button
that.slideTo($(this).attr('aria-controls')) // slide on click of button
})
}

Expand Down Expand Up @@ -98,10 +98,68 @@ export default class Showcase {
return console.error("could not find requested slide '" + rSlide + "'") // log an error
}

const $container = $(this.container)

// update aria-expanded to alert which slide is active
$container.find('button[aria-controls]').each(function() {
const $button = $(this)
if ($button.attr('aria-controls') === rSlide) {
$button.attr('aria-expanded', true)
}
else {
$button.attr('aria-expanded', false)
}
})

// we want to only mark them as hidden after some time has elapsed
// this allows us to keep the slide height animation
let $targetsToHide = [];
let $targetToFocus = false;

/**
* unhide a specific jQuery element
*
* we first set the hidden attribute to false, so that the DOM tree
* can update. we then set the active class on the next frame to allow
* the animation to play nicely
*
* @param {*} $target
*/
const unhide = function($target) {
$target.attr('hidden', false)
requestAnimationFrame(function() {
$target.addClass('active')
})
$target.find('.showcase-back').attr('tabindex', 0)
}

/**
* hide a specific jQuery element
*
* we don't need to worry about any animation frames here!
*
* @param {*} $target
*/
const hide = function($target) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inspecting this in FF when you click a button and go to the screenshot the entire list goes away in the a11y tree. Is that what we really want? I would think that we still want to mention that the list is there somehow and don't change the entire tree out from under a user.

(I have NOT tested this yet with a screenreader)

@ellieplayswow ellieplayswow Oct 15, 2026 •

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1st point: imo, it makes sense?

2nd point: it's out of the viewport in a way that you can only see it again is by going back, so why wouldn't it be what we want? in theory, the only "valid" action at the point of entering a tab is to go back, because there is no "go forwards". the current site has each link out of the viewport, so the only way you can navigate it is by seeing the links in the bottom change or just remembering the order.

since we remove the hidden attribute from the relevant showcase section and the buttons have an aria-controls, aria-expanded, a11y tools should announce the next action is to "go back".

if we want to keep the tree, then IMO the section needs some kind of redesign to keep each button visible and within the viewport at all times. i'm not sure what a preferred solution there would look like, especially when considering mobile.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just got back from a run, apologies if i came off as rude, not my intention :)

i largely agree that this should be seen as an accordion, and i think the convention would be that the title of the current item would remain focused even when expanded. but you also have to consider your available actions & what's visible. let's use this MDN playground as an example.

if you press <TAB> at the start, you select the first details item (the showcase equivalent would be the Music button). pressing <SPACE> opens that item.

image

you then have 2 options:

  1. press <SPACE> again, closing that tab (the showcase equivalent here would be the go back button);
  2. press <TAB>, moving to the next item

with my current showcase section, we don't have (1) as you're focusing the div itself, so the next <TAB> instead focuses the back button. you should, however, be informed that going back is an option. once you go back, (2) works just fine.

hope this makes a little more sense wrt how i'm thinking! ^^

$target.removeClass('active')
$target.find('.showcase-back').attr('tabindex', -1)
$targetsToHide.push($target)
}

const $indexSlide = $(this.index, this.container)

if (rSlide === 'index') {
$(this.index, this.container).addClass('active')
unhide($indexSlide)

if (this.current) {
// focus the previous button when returning to the index
$targetToFocus = $('button[aria-controls="' + this.current + '"]')
}
else {
$targetToFocus = $(this.index)
}
} else {
$(this.index, this.container).removeClass('active')
hide($indexSlide)
}

// iterates through slides based on this.slides
Expand All @@ -110,24 +168,33 @@ export default class Showcase {
const $n = $('#' + n, this.container) // current iterated slide

if (n === rSlide) { // if correct slide
$n.addClass('active')
unhide($n)
$targetToFocus = $n
} else {
$n.removeClass('active')
hide($n)
}
}

this.current = rSlide
$(this.container).trigger('change', {
$container.trigger('change', {
active: rSlide
})

if (this.current !== 'index') {
$('body').animate({
scrollTop: $(this.container).offset().top
scrollTop: $container.offset().top
}, 100)
}

this.resize() // resize the container

// hide and focus roughly after animations are complete
$container.one('transitionend', function() {
for (const $target of $targetsToHide) {
$target.attr('hidden', true)
}
$targetToFocus.focus()
})
}

/**
Expand Down
12 changes: 11 additions & 1 deletion _styles/home.css
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,6 @@ input:focus + .focus-reveal {
}

#showcase > div {
display: block;
margin: auto;
width: 100%;
padding: 0 24px;
Expand Down Expand Up @@ -220,6 +219,17 @@ input:focus + .focus-reveal {
color: inherit;
}

#showcase button {
background-color: transparent;
border: 0;
box-shadow: none;
background-image: none;
padding: 0;
margin: 0;
color: inherit;
text-shadow: inherit;
}

/********************
* showcase programs *
********************/
Expand Down
16 changes: 11 additions & 5 deletions _styles/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,16 @@ body {
**************/

a {
color: #08c;
fill: #08c;
color: #007dbc;
fill: #007dbc;
text-decoration: none;
}

section.grey a {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a specific link here that you are always trying to get to be the same in an SVG?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not sure what you mean? this styling just targets the grey sections, which had a slightly lower contrast ratio

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any action needed here?

color: #0079b6;
fill: #0079b6;
}

a:focus {
text-decoration: underline;
}
Expand Down Expand Up @@ -175,11 +180,11 @@ img.inline + div.inline {
margin-left: 12px;
}

.read-more {
.read-more button {
white-space: nowrap;
}

.read-more::after {
.read-more button::after {
color: inherit;

/* Encoded `→`, see: https://github.com/elementary/website/issues/2851 */
Expand All @@ -191,7 +196,8 @@ img.inline + div.inline {
transition: transform 0.15s ease-in-out;
}

.read-more:hover::after {
.read-more button:hover::after,
.read-more button:focus::after {
transform: translateX(3px);
}

Expand Down
2 changes: 1 addition & 1 deletion images/icons/actions/symbolic/appointment-symbolic.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion images/icons/actions/symbolic/edit-clear-all-symbolic.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion images/icons/status/symbolic/changes-prevent-symbolic.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading