diff --git a/framework-lid-workaround.nix b/framework-lid-workaround.nix
new file mode 100644
index 0000000..04445f1
--- /dev/null
+++ b/framework-lid-workaround.nix
@@ -0,0 +1,94 @@
+{ lib, pkgs, config, ... }:
+let
+  realpath = "${pkgs.coreutils}/bin/realpath";
+
+  cfg = config.hardware.framework.wakeOnInput;
+
+  # Write $1 into the power/wakeup attribute of all nodes that the udev rules
+  # matched
+  helper = pkgs.writeShellScript "framework-wakeup-helper.sh" ''
+    attrs="$(udevadm info --query property --property=DISABLE_WAKEUP_NODE --value /sys/class/input/input*)"
+
+    declare -A nodes=()
+
+    echo "Setting wakeup attrs to $1:"
+    for a in $attrs; do
+        if [[ "''${nodes[$a]}" ]]; then continue; fi
+        echo "  $a"
+        echo "$1" > "$a"/power/wakeup
+        nodes[$a]=true
+    done
+  '';
+in
+{
+  options.hardware.framework.wakeOnInput = lib.mkOption {
+    description = ''
+      When to allow the built-in keyboard and touchpad to wake the system
+
+      Takes one of three values: "always", "lid-open", or "never". If set to
+      "always", the laptop's keyboard and touchpad will wake the laptop. If set
+      to "lid-open" the laptop's keyboard and touchpad will only wake the
+      laptop if the lid was open when it entered suspend. If set to "never",
+      the laptop's keyboard and touchpad will never wake the laptop.
+
+      Note that the "lid-open" setting is best-effort. If the lid is closed
+      after the laptop is suspended, the touchpad and keyboard will still wake
+      it up!
+    '';
+
+    default = "always";
+    type = lib.types.enum [ "always" "lid-open" "never" ];
+  };
+
+  config = lib.mkIf (cfg != "always") {
+    # For each input device that might be responsible for waking the system,
+    # store the path of a device node that can have wakeups disabled.
+    # Consistently using input device nodes makes them easier for the helper
+    # script to find, and it seems reasonable to assume that any physical input
+    # capable of waking the system will have a corresponding input device.
+    services.udev.extraRules = ''
+      # Wakeup attr for the FW13 and FW16 touchpad's i2c device. The touchpad
+      # doesn't identify itself as a Framework product, so this rule matches
+      # any PixArt touchpad.
+      SUBSYSTEM=="input", DRIVERS=="i2c_hid_acpi", ATTR{phys}=="i2c-PIXA3854:00", PROGRAM=="${realpath} /sys%p/../../..", ENV{DISABLE_WAKEUP_NODE}="%c"
+
+      # Wakeup attr for any Framework USB HID devices, e.g. the FW16 keyboard
+      # modules. Only the ANSI keyboard has been tested.
+      SUBSYSTEM=="input", DRIVERS=="usbhid", ENV{ID_VENDOR_ID}=="32ac", PROGRAM=="${realpath} /sys%p/../../../..", ENV{DISABLE_WAKEUP_NODE}="%c"
+
+      # Wakeup attr for FW13 keyboard. Untested.
+      SUBSYSTEM=="input", DRIVERS=="atkbd", PROGRAM=="${realpath} /sys%p/../..", ENV{DISABLE_WAKEUP_NODE}="%c"
+    '';
+
+    systemd.services."framework-wakeup" = {
+      description = "Configure Framework keyboard/touchpad wakeup";
+
+      # If "always", run once during boot. If "lid-open", run before every
+      # suspend to check the lid state. NixOS doesn't have a good equivalent
+      # for /usr/lib/systemd/system-sleep, so we have to fake it.
+      wantedBy =
+        if cfg == "lid-open" then
+          [ "sleep.target" ]
+        else
+          [ "multi-user.target" ];
+      before = lib.mkIf (cfg == "lid-open") [ "sleep.target" ];
+
+      unitConfig.StopWhenUnneeded = true;
+
+      # Disable wakeups on all nodes that udev has discovered when the service
+      # starts (optionally checking lid state first); enable them when it
+      # exits.
+      serviceConfig = {
+        ExecStart =
+          if cfg == "lid-open" then
+            "/bin/sh -c 'if grep -q closed /proc/acpi/button/lid/LID*/state; then ${helper} disabled; fi'"
+          else
+            "${helper} disabled";
+        ExecStop = "${helper} enabled";
+
+        SyslogIdentifier = "framework-wakeup";
+        RemainAfterExit = true;
+      };
+    };
+  };
+}