Hotwire Native

Make delete actions stand out in Hotwire Native menus


The Hotwire Native menu bridge is one of the most common ways to interact with native functionality from your Hotwire Native app. It is part of the demo application and probably one of the first components you will implement when you start building your own Hotwire Native app. It instantly makes your app feel more native.

A common design pattern is to show ‘destructive’ actions (deleting a record, for example) in a different color (usually red) to indicate that the action is potentially dangerous and make them stand out from the other actions.

The example menu component has no way to do this out of the box, so in this article I will show you how to add different styling for such actions with just a few lines of code!

The current situation
What we'll end up with

The Bridge component (iOS)

All code is based on the example app’s MenuComponent and the corresponding Stimulus controller from the Hotwire Native demo app.

The menu is constructed from UIAlertAction classes in the iOS app’s MenuComponent. They allow for little configuration, but do have a style property that can be set to .destructive to indicate a destructive action, which makes the text of the item red. We’ll use this property to style our menu item differently.

The current implementation always uses the .default style, so we’re going to keep that as, well, the default.

To distinguish between regular and destructive menu items, we add a destructive boolean property to the Item struct (which is part of the data structure received from the bridge component), and set the style property of the UIAlertAction based on it’s value.

That leads to the following changes:

// MenuComponent.swift
// source: https://github.com/hotwired/hotwire-native-ios/blob/1.1.0/Demo/Bridge/MenuComponent.swift

// lines 39-43
for item in items {
-   let action = UIAlertAction(title: item.title, style: .default) { [unowned self] _ in
-       onItemSelected(item: item)
-   }
+   let action = UIAlertAction(title: item.title,
+                              style: item.destructive ? .destructive : .default)
+   { [unowned self] _ in
+       onItemSelected(item: item)
+   }
    alertController.addAction(action)
}

// ...

# line 89
struct Item: Decodable {
    let title: String
    let index: Int
+   let destructive: Bool
}

The formatting is a bit different, but the main functional change is the addition of the ternary operator that sets style based on the new property.

The Stimulus bridge controller

We now need to change the payload that is sent from the stimulus controller. The menuItem function inside the controller constructs the data for each menu item, so that’s where we need to add our new destructive property.

We could use a custom Stimulus value or data attribute and set it explicitly for the items we want to use it for. But since we already use Turbo there’s a good chance there’s already a data-turbo-method attribute set on link that do deletes, as Turbo uses that to send requests with the right HTTP request method.

We can leverage that attribute to set the destructive property directly. This means that all menu items that link to a DELETE action will automatically be marked as destructive, without changing any of the underlying HTML.

// app/javascript/controllers/bridge/menu_controller.js
// source: https://github.com/hotwired/hotwire-native-demo/blob/f5d60c343a1cf741f50aaec930e2ff17c267df26/public/javascript/controllers/bridge/menu_controller.js

// line 34
menuItem(element, index) {
  const bridgeElement = new BridgeElement(element)

  if (bridgeElement.disabled) return null

  return {
    title: bridgeElement.title,
    index: index,
+   destructive: element.dataset.turboMethod === "delete",
  }
}

That’s it! All menu items that link to a DELETE action will now be marked as destructive and shown in red in the native menu.

If anyone has written the Android variant of this component, please let me know! I didn’t dive into Android development yet, so haven’t written it yet myself.