Menus

Attach a native menu to any element on the page, like a list row, a card, or a status pill. Tapping the element opens the menu anchored right on it: a native UIMenu on iOS, a Material dropdown on Android. Picking an item navigates or clicks a web element, exactly like a navigation bar menu.

Menu
Todo list rows with vertical-ellipsis anchors on iOS
Closed
In-content menu open over a todo row on iOS
Open

#Basic usage

Wrap the items in native_menu_tag and point anchor: at a CSS selector for the element that opens it. The tag renders hidden; the anchor is an ordinary element your page already shows, like the vertical ellipsis on each row above. In a list, render one menu per record and lean on dom_id to keep the selectors unique:

<span id="<%= dom_id(todo, :menu) %>" aria-hidden="true">&#8942;</span>

<%= native_menu_tag anchor: "##{dom_id(todo, :menu)}" do |menu| %>
  <% menu.item "Complete", click: "##{dom_id(todo, :complete)}", icons: { ios: "checkmark.circle", android: "check_circle" } %>
  <% menu.item "Edit",     href: edit_todo_path(todo),           icons: { ios: "pencil", android: "edit" } %>
  <% menu.item "Delete",   click: "##{dom_id(todo, :delete)}",   icons: { ios: "trash", android: "delete" }, destructive: true %>
<% end %>

<%# Keep the web elements in the DOM so the native menu can click them %>
<%= button_to "Complete", complete_todo_path(todo), method: :patch,  id: dom_id(todo, :complete), form: { hidden: true } %>
<%= button_to "Delete",   todo_path(todo),          method: :delete, id: dom_id(todo, :delete),   form: { hidden: true } %>
import { NativeMenu, NativeMenuItem } from "@ruby-native/react"

export default function TodoMenu({ todo, onComplete, onDelete }) {
  return (
    <>
      <span id={`menu-todo-${todo.id}`} aria-hidden="true">&#8942;</span>

      <NativeMenu anchor={`#menu-todo-${todo.id}`}>
        <NativeMenuItem title="Complete" click={`#complete-todo-${todo.id}`} icons={{ ios: "checkmark.circle", android: "check_circle" }} />
        <NativeMenuItem title="Edit" href={`/todos/${todo.id}/edit`} icons={{ ios: "pencil", android: "edit" }} />
        <NativeMenuItem title="Delete" click={`#delete-todo-${todo.id}`} icons={{ ios: "trash", android: "delete" }} destructive />
      </NativeMenu>

      {/* Keep the web elements in the DOM so the native menu can click them */}
      <button id={`complete-todo-${todo.id}`} onClick={onComplete} hidden>Complete</button>
      <button id={`delete-todo-${todo.id}`} onClick={onDelete} hidden>Delete</button>
    </>
  )
}
<script setup>
import { NativeMenu, NativeMenuItem } from "@ruby-native/vue"
defineProps(["todo"])
defineEmits(["complete", "delete"])
</script>

<template>
  <span :id="`menu-todo-${todo.id}`" aria-hidden="true">&#8942;</span>

  <NativeMenu :anchor="`#menu-todo-${todo.id}`">
    <NativeMenuItem title="Complete" :click="`#complete-todo-${todo.id}`" :icons="{ ios: 'checkmark.circle', android: 'check_circle' }" />
    <NativeMenuItem title="Edit" :href="`/todos/${todo.id}/edit`" :icons="{ ios: 'pencil', android: 'edit' }" />
    <NativeMenuItem title="Delete" :click="`#delete-todo-${todo.id}`" :icons="{ ios: 'trash', android: 'delete' }" destructive />
  </NativeMenu>

  <!-- Keep the web elements in the DOM so the native menu can click them -->
  <button :id="`complete-todo-${todo.id}`" @click="$emit('complete')" hidden>Complete</button>
  <button :id="`delete-todo-${todo.id}`" @click="$emit('delete')" hidden>Delete</button>
</template>

A page can carry several menus, each with its own anchor:. Tapping outside the menu dismisses it without running anything, and so does the system back gesture on Android. A destructive item renders red on both platforms; keep any confirmation where it belongs, on the server or in your web JavaScript.

Items take the same options as navigation bar menu items:

Option Type Description
title string The label shown in the menu.
href string URL to navigate to when selected.
click string CSS selector of a DOM element to .click() when selected.
icon string Optional icon name shown next to the title.
icons hash Per-platform icons, e.g. { ios: "pencil", android: "edit" }. A match overrides icon.
selected boolean Renders a checkmark next to the item.
destructive boolean Renders the item red, for delete-style actions.
action symbol :replace visits href without stacking a back button, for switchers.