WordPress Nav Walker: eigenes Feld für aria-label

Lighthouse meckert über nicht-beschreibende Link-Texte in der Navigation. „WordPress“, „Service“, „Blog“ – klar, was gemeint ist, aber ein Screenreader weiß ohne Kontext nicht wohin der Link führt. Die Lösung: ein eigenes Feld im Menü-Editor, das ein aria-label pro Menü-Item aufnimmt.

Das Ziel

Im WordPress-Menü-Editor soll ein neues Textfeld erscheinen: „Aria-Label“. Wenn es befüllt ist, gibt der Walker das aria-label-Attribut am Link aus. Wenn es leer bleibt, passiert nichts – der Link verhält sich wie gehabt.

Schritt 1: Custom Field im Menü-Editor registrieren

WordPress stellt dafür den Action-Hook wp_nav_menu_item_custom_fields bereit. Der wird für jedes Menü-Item aufgerufen und gibt die Item-ID mit. Alles landet direkt in der functions.php des Themes:

function prefix_menu_item_aria_label_field( $item_id, $item ) {
	$value = get_post_meta( $item_id, '_prefix_menu_aria_label', true );
	?>
	<p class="field-aria-label description description-wide">
		<label for="edit-menu-item-aria-label-<?php echo $item_id; ?>">
			Aria-Label
			<input
				type="text"
				id="edit-menu-item-aria-label-<?php echo $item_id; ?>"
				class="widefat edit-menu-item-aria-label"
				name="menu-item-aria-label[<?php echo $item_id; ?>]"
				value="<?php echo esc_attr( $value ); ?>"
			/>
		</label>
	</p>
	<?php
}
add_action( 'wp_nav_menu_item_custom_fields', 'prefix_menu_item_aria_label_field', 10, 2 );

Das Feld wird als Post-Meta unter dem Schlüssel _prefix_menu_aria_label gespeichert. Der Unterstrich am Anfang verhindert, dass es in der normalen Custom-Fields-Ansicht auftaucht.

Schritt 2: Custom Field speichern

Beim Speichern des Menüs feuert wp_update_nav_menu_item. Hier lese ich den POST-Wert aus und speichere ihn – oder lösche das Meta wenn das Feld geleert wurde:

function prefix_menu_item_aria_label_save( $menu_id, $menu_item_db_id ) {
	if ( ! isset( $_POST['menu-item-aria-label'][ $menu_item_db_id ] ) ) {
		return;
	}
	$value = sanitize_text_field( $_POST['menu-item-aria-label'][ $menu_item_db_id ] );
	if ( ! empty( $value ) ) {
		update_post_meta( $menu_item_db_id, '_prefix_menu_aria_label', $value );
	} else {
		delete_post_meta( $menu_item_db_id, '_prefix_menu_aria_label' );
	}
}
add_action( 'wp_update_nav_menu_item', 'prefix_menu_item_aria_label_save', 10, 2 );

Schritt 3: Custom Walker

Der Standard-Walker von WordPress kennt das Feld nicht – ich brauche einen eigenen. Die Klasse erweitert Walker_Nav_Menu und überschreibt nur start_el():

class Prefix_Nav_Walker extends Walker_Nav_Menu {

	public function start_el( &$output, $item, $depth = 0, $args = null, $id = 0 ) {
		$aria_label = get_post_meta( $item->ID, '_prefix_menu_aria_label', true );

		$aria_attr = '';
		if ( ! empty( $aria_label ) ) {
			$aria_attr = ' aria-label="' . esc_attr( $aria_label ) . '"';
		}

		$atts = array(
			'href'   => ! empty( $item->url ) ? $item->url : '#',
			'target' => ! empty( $item->target ) ? $item->target : '',
			'rel'    => ! empty( $item->xfn ) ? $item->xfn : '',
			'class'  => 'nav-link',
		);

		$attributes = '';
		foreach ( $atts as $attr => $value ) {
			if ( ! empty( $value ) ) {
				$attributes .= ' ' . $attr . '="' . esc_attr( $value ) . '"';
			}
		}

		$link_before = isset( $args->link_before ) ? $args->link_before : '';
		$link_after  = isset( $args->link_after ) ? $args->link_after : '';
		$title       = apply_filters( 'the_title', $item->title, $item->ID );

		$output .= '<li' . $this->build_css_class( $item, $depth ) . '>';
		$output .= '<a' . $attributes . $aria_attr . '>';
		$output .= $link_before . esc_html( $title ) . $link_after;
		$output .= '</a>';
	}

	private function build_css_class( $item, $depth ) {
		$classes     = empty( $item->classes ) ? array() : (array) $item->classes;
		$classes[]   = 'menu-item-' . $item->ID;
		$class_names = implode( ' ', array_filter( $classes ) );
		return $class_names ? ' class="' . esc_attr( $class_names ) . '"' : '';
	}
}

Schritt 4: Walker einbinden

Alle drei Teile – Field-Registrierung, Save-Hook und Walker-Klasse – kommen direkt in die functions.php des Themes. Keine extra Datei, kein Plugin. Danach in den wp_nav_menu()-Args den neuen Walker übergeben:

'walker' => new Prefix_Nav_Walker(),

Das war es. Im Menü-Editor erscheint jetzt unter jedem Item das Feld „Aria-Label“. Befüllt – wird es ausgegeben. Leer – kein Attribut, kein Overhead.

Fazit

Ein Custom Nav Walker klingt nach Aufwand, ist aber überschaubar. Die Alternative – Link-Texte in der Navigation umbenennen – ist meistens keine Option wenn das visuelle Design kurze Labels fordert. Drei Funktionen in der functions.php, einmal den Walker eintragen – fertig.