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: 2 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ def update_tree() -> None:
</script>
''')

ui.skip_to_main()

custom_sub_pages({
'/': main_page.create,
'/examples': examples_page.create,
Expand Down
37 changes: 37 additions & 0 deletions nicegui/elements/skip_to_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from ..context import context
from .mixins.text_element import TextElement


class SkipToMain(TextElement, default_classes='nicegui-skip-to-main'):

def __init__(self) -> None:
"""Skip to main content

An accessibility button that is hidden until focused via keyboard (Tab).
It allows keyboard users to skip navigation and jump directly to the main content.

Optionally, use it as a context manager to customize text and styling.

Note: The skip button is automatically placed before other layout elements in the DOM,
with the exception of other skip buttons, which are placed in-order-of-creation.
"""
with context.client.layout:
super().__init__(tag='button', text='Skip to main content')

assert self.parent_slot is not None
for i, element in enumerate(self.parent_slot.children):
if not isinstance(element, SkipToMain):
self.move(target_index=i)
break

self.on('click', js_handler='''(e) => {
const el = document.getElementById('c' + e.target.dataset.target);
el.setAttribute('tabindex', '-1');
el.focus();
}''')
self._props['data-target'] = self.client.next_element_id

def __exit__(self, *_):
self.text = ''
self._props['data-target'] = self.client.next_element_id
return super().__exit__(*_)
12 changes: 12 additions & 0 deletions nicegui/static/nicegui.css
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,18 @@
display: none;
}

/* skip to main content link */
.nicegui-skip-to-main {
position: absolute;
left: -9999px;
}
.nicegui-skip-to-main:focus {
position: fixed;
top: 0;
left: 0;
z-index: 9999;
}

/* other NiceGUI elements */
.nicegui-grid {
display: grid;
Expand Down
2 changes: 2 additions & 0 deletions nicegui/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
'select',
'separator',
'skeleton',
'skip_to_main',
'slide_item',
'slider',
'space',
Expand Down Expand Up @@ -229,6 +230,7 @@
from .elements.select import Select as select
from .elements.separator import Separator as separator
from .elements.skeleton import Skeleton as skeleton
from .elements.skip_to_main import SkipToMain as skip_to_main
from .elements.slide_item import SlideItem as slide_item
from .elements.slider import Slider as slider
from .elements.space import Space as space
Expand Down
37 changes: 37 additions & 0 deletions tests/test_skip_to_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from nicegui import ui
from nicegui.testing import Screen


def test_skip_to_main(screen: Screen):
target = None

@ui.page('/')
def page():
nonlocal target
ui.skip_to_main()
target = ui.label('Main content')

screen.open('/')
button = screen.find_by_class('nicegui-skip-to-main')
assert button.get_attribute('innerText') == 'Skip to main content'
assert button.get_attribute('data-target') == str(target.id)

screen.selenium.execute_script('arguments[0].focus(); arguments[0].click();', button)
screen.wait(0.5)
assert screen.selenium.switch_to.active_element.get_attribute('id') == f'c{target.id}'


def test_context_manager(screen: Screen):
after = None

@ui.page('/')
def page():
nonlocal after
with ui.skip_to_main():
ui.label('Jump to content')
after = ui.button('After skip')

screen.open('/')
button = screen.find_by_class('nicegui-skip-to-main')
assert 'Jump to content' in button.get_attribute('innerText')
assert button.get_attribute('data-target') == str(after.id)
2 changes: 2 additions & 0 deletions website/documentation/content/section_page_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
scroll_area_documentation,
separator_documentation,
skeleton_documentation,
skip_to_main_documentation,
space_documentation,
splitter_documentation,
stepper_documentation,
Expand Down Expand Up @@ -92,6 +93,7 @@ def add_face():
doc.intro(separator_documentation)
doc.intro(space_documentation)
doc.intro(skeleton_documentation)
doc.intro(skip_to_main_documentation)
doc.intro(splitter_documentation)
doc.intro(tabs_documentation)
doc.intro(stepper_documentation)
Expand Down
39 changes: 39 additions & 0 deletions website/documentation/content/skip_to_main_documentation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from nicegui import ui

from . import doc


@doc.auto_execute
@doc.demo(ui.skip_to_main)
def main_demo() -> None:
@ui.page('/skip_to_main_demo')
def skip_to_main_demo():
ui.skip_to_main()
ui.label('Press Tab to reveal the skip button, then Enter to jump here.')

# @ui.page('/')
def page():
ui.link('show page with skip-to-main button', skip_to_main_demo)
page() # HIDE


@doc.auto_execute
@doc.demo('Custom content', '''
Use ``ui.skip_to_main`` as a context manager to customize the button text and styling.
The skip target will be set to the element created right after the context manager block.
''')
def custom_content_demo() -> None:
@ui.page('/skip_to_main_custom_demo')
def skip_to_main_custom_demo():
with ui.skip_to_main().classes('bg-primary text-white p-2'), ui.row(align_items='center'):
ui.icon('skip_next').classes('text-2xl')
ui.label('Jump to main content')
ui.label('Main content starts here')

# @ui.page('/')
def page():
ui.link('show page with custom skip button', skip_to_main_custom_demo)
page() # HIDE


doc.reference(ui.skip_to_main)