Keyboard Hints

Components for displaying keyboard shortcut hints to users.

Single Hint Badge


source

create_modifier_key_hint


def create_modifier_key_hint(
    modifier:str, # modifier key name (e.g., "shift", "ctrl")
    key_icon_or_text:Union[str, FT], # the main key icon or text
    description:str, # action description
    style:str='ghost', # badge style
)->Div: # hint badge with modifier + key

Create a hint badge with a modifier key and main key.


source

create_nav_icon_hint


def create_nav_icon_hint(
    icon_name:str, # lucide icon name (e.g., "arrow-down-up")
    description:str, # action description
    style:str='ghost', # badge style
)->Div: # hint badge with icon

Create a hint badge with a lucide icon.


source

render_hint_badge


def render_hint_badge(
    key_display:Union[str, FT], # formatted key string or icon component
    description:str, # action description
    style:str='ghost', # badge style (ghost, outline, soft, dash)
    auto_icon:bool=False, # auto-convert known keys to icons
)->Div: # hint badge component

Render a single keyboard hint as a badge.


source

get_key_icon


def get_key_icon(
    key_name:str, # key name to look up (case-insensitive)
    size:int=3, # icon size
)->FT | None: # icon component or None if no icon mapping

Get a lucide icon for a key name, if one exists.

# Test hint badge with string
from fasthtml.common import to_xml

hint_badge = render_hint_badge("Space", "Select")
html = to_xml(hint_badge)
assert "Space" in html
assert "Select" in html
assert "badge" in html

# Test hint badge with icon
icon_hint = create_nav_icon_hint("arrow-down-up", "Navigate")
html = to_xml(icon_hint)
assert "Navigate" in html
assert "svg" in html  # Icon is an SVG

# Test auto_icon conversion
delete_hint = render_hint_badge("Delete", "Remove item", auto_icon=True)
html = to_xml(delete_hint)
assert "svg" in html  # Should convert to trash icon
assert "Remove item" in html

# Test get_key_icon
assert get_key_icon("shift") is not None
assert get_key_icon("delete") is not None
assert get_key_icon("unknown_key") is None

# Test modifier key hint (Shift + arrow)
shift_nav = create_modifier_key_hint("shift", lucide_icon("arrow-down-up", size=3), "Reorder")
html = to_xml(shift_nav)
assert "svg" in html
assert "Reorder" in html

Hint Group


source

render_hint_group


def render_hint_group(
    group_name:str, # group header text
    hints:list[tuple[str, str]], # list of (key_display, description) tuples
    badge_style:str='ghost', # badge style for this group
)->Div: # group container with header and hints

Render a group of related keyboard hints.

# Test hint group with string keys
group = render_hint_group(
    "Navigation",
    [("W/S", "Move"), ("A/D", "Switch")]
)
html = to_xml(group)
assert "Navigation" in html
assert "Move" in html
assert "Switch" in html

Hints from Actions


source

group_actions_by_hint_group


def group_actions_by_hint_group(
    actions:tuple[KeyAction, ...], # actions to group
)->dict[str, list[KeyAction]]: # grouped actions

Group actions by their hint_group attribute.


source

render_hints_from_actions


def render_hints_from_actions(
    actions:tuple[KeyAction, ...], # actions to display hints for
    badge_style:str='ghost', # badge style
)->Div: # container with all hint groups

Render keyboard hints from action configurations.

# Test hints from actions
actions = (
    KeyAction(key=" ", htmx_trigger="x", description="Select", hint_group="Selection"),
    KeyAction(key="Delete", htmx_trigger="y", description="Remove", hint_group="Actions"),
    KeyAction(key="Enter", js_callback="z", description="Open", hint_group="Actions"),
    KeyAction(key="Backspace", htmx_trigger="w", description="Delete", show_in_hints=False),  # Hidden
)

hints_component = render_hints_from_actions(actions)
html = to_xml(hints_component)
assert "Selection" in html
assert "Actions" in html
assert "Select" in html
assert "Remove" in html
assert "Delete" not in html  # show_in_hints=False

Full Keyboard Hints


source

render_keyboard_hints


def render_keyboard_hints(
    manager:ZoneManager, # the zone manager
    include_navigation:bool=True, # include navigation hints
    include_zone_switch:bool=True, # include zone switching hints
    badge_style:str='ghost', # badge style
    container_id:str='kb-hints', # container element ID
    use_icons:bool=True, # use lucide icons for nav hints
)->Div: # complete hints component

Render complete keyboard hints for a zone manager.

# Test full keyboard hints with icons (default)
from cjm_fasthtml_keyboard_navigation.core.focus_zone import FocusZone

zone1 = FocusZone(id="z1")
zone2 = FocusZone(id="z2")

manager = ZoneManager(
    zones=(zone1, zone2),
    actions=(
        KeyAction(key=" ", htmx_trigger="toggle", description="Select", hint_group="Selection"),
    )
)

hints = render_keyboard_hints(manager)
html = to_xml(hints)

assert 'id="kb-hints"' in html
assert "Navigate" in html
assert "Switch Panel" in html  # Two zones = show zone switch
assert "Select" in html
assert "svg" in html  # Icons are SVGs

# Test without icons
hints_no_icons = render_keyboard_hints(manager, use_icons=False)
html_no_icons = to_xml(hints_no_icons)
assert "↑/↓" in html_no_icons  # Text arrows when icons disabled
# Single zone = no zone switch hint
single_manager = ZoneManager(zones=(zone1,), actions=())
hints = render_keyboard_hints(single_manager)
html = to_xml(hints)
assert "Switch Panel" not in html