css only menu tests

Various methods of css only menu browsing.

by Laurens Maneschijn

HTML

<input style="width:1px; height:1px;" onfocus="this.nextElementSibling.focus();">
<input style="width:1px; height:1px;">
<small>&larr; shift-tab focus catcher</small>

<h1>Hover menu:</h1>

<div class="block menu_hover">
	<m>
		&equiv;
		<m>
			item 1
			<m>
				item 1.1
				<m>
					item 1.1.1
				</m>
				<m>
					item 1.1.2
				</m>
			</m>
			<m>
				item 1.2
				<m>
					item 1.2.1
				</m>
				<m>
					item 1.2.2
				</m>
			</m>
		</m>
		<m>
			item 2
			<m>
				item 2.1
				<m>
					item 2.1.1
				</m>
				<m>
					item 2.1.2
				</m>
			</m>
			<m>
				item 2.2
				<m>
					item 2.2.1
				</m>
				<m>
					item 2.2.2
				</m>
			</m>
		</m>
	</m>
</div>

<h1>Focus menu: click or tab through elements:</h1>
<small>
Almost same as above, but uses :focus instead of :hover, 
and all elements have &lt; tabindex="0"&gt;.<br>
</small>

<div class="block menu_focus">
	<m tabindex="0">
		&equiv;
		<m tabindex="0">
			item 1
			<m tabindex="0">
				item 1.1
				<m tabindex="0">
					item 1.1.1
				</m>
				<m tabindex="0">
					item 1.1.2
				</m>
			</m>
			<m tabindex="0">
				item 1.2
				<m tabindex="0">
					item 1.2.1
				</m>
				<m tabindex="0">
					item 1.2.2
				</m>
			</m>
		</m>
		<m tabindex="0">
			item 2
			<m tabindex="0">
				item 2.1
				<m tabindex="0">
					item 2.1.1
				</m>
				<m tabindex="0">
					item 2.1.2
				</m>
			</m>
			<m tabindex="0">
				item 2.2
				<m tabindex="0">
					item 2.2.1
				</m>
				<m tabindex="0">
					item 2.2.2
				</m>
			</m>
		</m>
	</m>
</div>

<h1>Focus menu v2: click or tab through elements:</h1>

<small>
(same as before, but now with seperate "handle" elements)<br>
</small>

<div class="block menu_focus_v2">
	<m>
		<ml tabindex="0">&equiv;</ml>
		<m>
			<ml tabindex="0">item 1</ml>
			<m>
				<ml tabindex="0">item 1.1</ml>
				<m>
					<ml tabindex="0">item 1.1.1</ml>
					<ml tabindex="0">item 1.1.2</ml>
				</m>
				<ml tabindex="0">item 1.2</ml>
				<m>
					<ml tabindex="0">item 1.2.1</ml>
					<ml...

SCSS

/* --- menu unrelated css: --- */
h1,h2,h3,h4,h5,h6 {
	font-size: 100%;
	margin: 10px 0 0 0;
}

.block {
	margin: 4px;
	padding: 4px;
	border: 1px solid #888;
	border-radius: 4px;
}

/* simple hover menu: */
.menu_hover {
	/* just some fancy display: */
	m {
		display: inline-block;
		margin: 4px;
		padding: 4px;
		border: 1px solid #888;
		border-radius: 4px;
		background: #8888;
	}

	/* highlight current hover element: */
	m:hover:not(:has(:hover)) {
		border: 1px dashed #000;
	}

	/* handle show and hide of elements: */
	m > m {
		display: none;
	}

	m:hover > m {
		display: block;
	}
}

/* focus variant, click or tab-able: */
.menu_focus {
	/* just some fancy display: */
	m {
		display: inline-block;
		margin: 4px;
		padding: 4px;
		border: 1px solid #888;
		border-radius: 4px;
		background: #8888;
	}

	/* highlight current focus element: */
	m:focus {
		border: 1px dashed #000;
	}

	/* handle show and hide of elements: */
	m > m {
		display: none;
	}

	/*
	:has() only supports basic selectors inside.
	:has(:focus) might not be supported, but
	:focus-within does the same and is supported in most browsers.
	*/
	m:has(:focus) > m,
	m:focus-within > m,
	m:focus > m {
		display: block;
	}
}

/*
--- focus variant 2. click or tab-able:
Same as before, but now with seperate handle elements:

.menu_focus_v3
	m
		ml (handle)
		m (content)
			ml (handle)
			m (content)
				...
*/
.menu_focus_v2 {
	m {
		display: inline-block;
		margin: 4px;
		padding: 4px;
		border: 1px solid #888;
		border-radius: 4px;
		background: #8888;
	}

	ml {
		display: block;
	}

	ml:focus {
		border: 1px dashed #000;
	}
/*
	this almost works, but shows all m in same depth
	m:not(:focus-within) m {
		display: none;
	}
*/

	m m {
		display: none;
	}
	ml:focus + m {
		display: block;
	}
	/* this should work, but does not somehow: */
	m m:has(ml:focus),
	m m:focus-within {
		display: block !important;
	}

}

/* --- focus variant 3. click or tab-able: 
.menu_focus_v3
  m
	  mhandle
		mcontent
		 ...

JavaScript

// onload focus on first element for easy tab-focus testing:
document.querySelector('input,[tabindex]').focus();



// some test debug things below:

/*

// log which elements get focus for debugging:
const getElementTextWithoutChildElements = el => [].reduce.call(el.childNodes, (a, b) => a + (b.nodeType === 3 ? b.textContent : ''), '');
document.querySelectorAll('*').forEach(el => {
	el.addEventListener('focus', e => {
		console.log('focus', getElementTextWithoutChildElements(el).trim(), el, e);
	});
});

// Test focus on page load on some elements:
//	const q = document.querySelector.bind(document);
//	const q = s => document.querySelector(s);
//	const f = s => document.querySelector(s).focus();
//	const f = s => q(s).focus();
//	f('.menu_focus > m');           // should focus ≡       , and works.
//	f('.menu_focus > m > m');       // should focus item 1  , but does not work.
//	f('.menu_focus2 > m > ml');     // should focus ≡       , and works.
//	f('.menu_focus2 > m > m > ml'); // should focus item 1  , but does not work.

*/