diff --git a/data/gresource.xml b/data/gresource.xml
index 3baf25c1..326a92bc 100644
--- a/data/gresource.xml
+++ b/data/gresource.xml
@@ -4,6 +4,9 @@
Application.css
+ icons/usb.svg
+ icons/usb.svg
+
icons/screencast.svg
icons/screencast.svg
diff --git a/data/icons/usb.svg b/data/icons/usb.svg
new file mode 100644
index 00000000..abf7fc36
--- /dev/null
+++ b/data/icons/usb.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/data/pantheon.portal b/data/pantheon.portal
index b9de94e3..91020daf 100644
--- a/data/pantheon.portal
+++ b/data/pantheon.portal
@@ -1,4 +1,4 @@
[portal]
DBusName=org.freedesktop.impl.portal.desktop.pantheon
-Interfaces=org.freedesktop.impl.portal.Access;org.freedesktop.impl.portal.AppChooser;org.freedesktop.impl.portal.Background;org.freedesktop.impl.portal.Screenshot;org.freedesktop.impl.portal.Wallpaper;org.freedesktop.impl.portal.ScreenCast
+Interfaces=org.freedesktop.impl.portal.Access;org.freedesktop.impl.portal.AppChooser;org.freedesktop.impl.portal.Background;org.freedesktop.impl.portal.Screenshot;org.freedesktop.impl.portal.Usb;org.freedesktop.impl.portal.Wallpaper;org.freedesktop.impl.portal.ScreenCast
UseIn=pantheon
diff --git a/po/POTFILES b/po/POTFILES
index e43f71dc..b3f60bec 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -10,3 +10,5 @@ src/Background/Portal.vala
src/ScreenCast/Dialog.vala
src/ScreenCast/MonitorTracker/Interface.vala
src/Screenshot/SetupDialog.vala
+src/Usb/Dialog.vala
+src/Usb/Portal.vala
diff --git a/src/PortalDialog.vala b/src/PortalDialog.vala
index 9dc7bdc4..bbb811fe 100644
--- a/src/PortalDialog.vala
+++ b/src/PortalDialog.vala
@@ -54,7 +54,20 @@ public class PortalDialog : Gtk.Window, PantheonWayland.ExtendedBehavior {
public enum ResponseType {
ALLOW,
CANCEL,
- DELETE_EVENT
+ DELETE_EVENT;
+
+ // Convert to response ID expected by portal backends
+ public uint32 to_id () {
+ switch (this) {
+ case ALLOW:
+ return 0;
+ case CANCEL:
+ return 1;
+ default:
+ case DELETE_EVENT:
+ return 2;
+ }
+ }
}
/**
diff --git a/src/Usb/Dialog.vala b/src/Usb/Dialog.vala
new file mode 100644
index 00000000..8f629e59
--- /dev/null
+++ b/src/Usb/Dialog.vala
@@ -0,0 +1,44 @@
+/*
+ * SPDX-FileCopyrigthText: 2026 elementary, Inc. (https://elementary.io)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+[DBus (name = "org.freedesktop.impl.portal.Request")]
+public class Usb.Dialog : PortalDialog {
+ // The ID used to register this dialog on the DBusConnection
+ public uint register_id { get; set; default = 0; }
+
+ // The ID of the app sending the request
+ public string app_id { get; construct; }
+
+ public string reason {
+ set {
+ secondary_text = _("It provided the following reason, ā%sā").printf (value);
+ }
+ }
+
+ public Dialog (string app_id) {
+ Object (app_id: app_id);
+ }
+
+ construct {
+ default_height = -1;
+ title = _("An application wants to access the following USB devices");
+ secondary_icon = new ThemedIcon ("emblem-portal-usb");
+ secondary_text = _("It did not provide a reason for this request.");
+
+ if (app_id != "") {
+ var app_info = new DesktopAppInfo (app_id + ".desktop");
+ if (app_info != null) {
+ primary_icon = app_info.get_icon ();
+ title = _("ā%sā wants to access the following USB devices").printf (app_info.get_display_name ());
+ }
+ }
+
+ }
+
+ [DBus (name = "Close")]
+ public void on_close () throws DBusError, IOError {
+ response (DELETE_EVENT);
+ }
+}
diff --git a/src/Usb/Portal.vala b/src/Usb/Portal.vala
new file mode 100644
index 00000000..5020d77b
--- /dev/null
+++ b/src/Usb/Portal.vala
@@ -0,0 +1,65 @@
+/*
+ * SPDX-FileCopyrigthText: 2026 elementary, Inc. (https://elementary.io)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+[DBus (name = "org.freedesktop.impl.portal.Usb")]
+public class Usb.Portal : Object {
+ private DBusConnection connection;
+
+ public Portal (DBusConnection connection) {
+ this.connection = connection;
+ }
+
+ [DBus (name = "AcquireDevices")]
+ public async void acquire_devices (
+ ObjectPath handle,
+ string parent_window,
+ string app_id,
+ // G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE ("a(sa{sv})"));
+ // IN devices a(sa{sv}a{sv}),
+ HashTable options,
+ out uint response,
+ out HashTable results
+ ) throws DBusError, IOError {
+ var dialog = new Dialog (app_id) {
+ parent_handle = parent_window
+ };
+
+ if ("reason" in options) {
+ dialog.reason = options["reason"].get_string ();
+ }
+
+ try {
+ dialog.register_id = connection.register_object