diff --git a/modules/services/window-managers/i3-sway/sway.nix b/modules/services/window-managers/i3-sway/sway.nix
index 14d82d267..31ba16a06 100644
--- a/modules/services/window-managers/i3-sway/sway.nix
+++ b/modules/services/window-managers/i3-sway/sway.nix
@@ -153,7 +153,12 @@ let
         default = { };
         example = { "*" = { xkb_variant = "dvorak"; }; };
         description = ''
-          An attribute set that defines input modules. See man sway_input for options.
+          An attribute set that defines input modules. See
+          <citerefentry>
+            <refentrytitle>sway-input</refentrytitle>
+            <manvolnum>5</manvolnum>
+          </citerefentry>
+          for options.
         '';
       };
 
@@ -162,7 +167,26 @@ let
         default = { };
         example = { "HDMI-A-2" = { bg = "~/path/to/background.png fill"; }; };
         description = ''
-          An attribute set that defines output modules. See man sway_output for options.
+          An attribute set that defines output modules. See
+          <citerefentry>
+            <refentrytitle>sway-output</refentrytitle>
+            <manvolnum>5</manvolnum>
+          </citerefentry>
+          for options.
+        '';
+      };
+
+      seat = mkOption {
+        type = types.attrsOf (types.attrsOf types.str);
+        default = { };
+        example = { "*" = { hide_cursor = "when-typing enable"; }; };
+        description = ''
+          An attribute set that defines seat modules. See
+          <citerefentry>
+            <refentrytitle>sway-input</refentrytitle>
+            <manvolnum>5</manvolnum>
+          </citerefentry>
+          for options.
         '';
       };
 
@@ -227,19 +251,15 @@ let
     ${if always then "exec_always" else "exec"} ${command}
   '';
 
-  inputStr = name: attrs: ''
-    input "${name}" {
-    ${concatStringsSep "\n"
-    (mapAttrsToList (name: value: "${name} ${value}") attrs)}
-    }
-  '';
-
-  outputStr = name: attrs: ''
-    output "${name}" {
+  moduleStr = moduleType: name: attrs: ''
+    ${moduleType} "${name}" {
     ${concatStringsSep "\n"
     (mapAttrsToList (name: value: "${name} ${value}") attrs)}
     }
   '';
+  inputStr = moduleStr "input";
+  outputStr = moduleStr "output";
+  seatStr = moduleStr "seat";
 
   configFile = pkgs.writeText "sway.conf" ((if cfg.config != null then
     with cfg.config; ''
@@ -274,15 +294,19 @@ let
           lib.optionalString (cfg.config.bindkeysToCode) "--to-code";
       }}
       ${keycodebindingsStr keycodebindings}
-      ${concatStringsSep "\n" (mapAttrsToList inputStr input)}
-      ${concatStringsSep "\n" (mapAttrsToList outputStr output)}
-      ${concatStringsSep "\n" (mapAttrsToList modeStr modes)}
-      ${concatStringsSep "\n" (mapAttrsToList assignStr assigns)}
-      ${concatStringsSep "\n" (map barStr bars)}
-      ${optionalString (gaps != null) gapsStr}
-      ${concatStringsSep "\n" (map floatingCriteriaStr floating.criteria)}
-      ${concatStringsSep "\n" (map windowCommandsStr window.commands)}
-      ${concatStringsSep "\n" (map startupEntryStr startup)}
+      ${concatStringsSep "\n" (
+        # Append all of the lists together to avoid unnecessary whitespace.
+        mapAttrsToList inputStr input # inputs
+        ++ mapAttrsToList outputStr output # outputs
+        ++ mapAttrsToList seatStr seat # seats
+        ++ mapAttrsToList modeStr modes # modes
+        ++ mapAttrsToList assignStr assigns # assigns
+        ++ map barStr bars # bars
+        ++ optional (gaps != null) gapsStr # gaps
+        ++ map floatingCriteriaStr floating.criteria # floating
+        ++ map windowCommandsStr window.commands # window commands
+        ++ map startupEntryStr startup # startup
+      )}
     ''
   else
     "") + "\n" + (if cfg.systemdIntegration then ''
diff --git a/tests/modules/services/window-managers/sway/default.nix b/tests/modules/services/window-managers/sway/default.nix
index c511e64bd..f81b510fe 100644
--- a/tests/modules/services/window-managers/sway/default.nix
+++ b/tests/modules/services/window-managers/sway/default.nix
@@ -4,4 +4,5 @@
   sway-followmouse = ./sway-followmouse.nix;
   sway-followmouse-legacy = ./sway-followmouse-legacy.nix;
   sway-null-package = ./sway-null-package.nix;
+  sway-modules = ./sway-modules.nix;
 }
diff --git a/tests/modules/services/window-managers/sway/sway-default.conf b/tests/modules/services/window-managers/sway/sway-default.conf
index da5b1f47e..bb0317b7e 100644
--- a/tests/modules/services/window-managers/sway/sway-default.conf
+++ b/tests/modules/services/window-managers/sway/sway-default.conf
@@ -69,8 +69,6 @@ bindsym Mod1+space focus mode_toggle
 bindsym Mod1+v splitv
 bindsym Mod1+w layout tabbed
 
-
-
 mode "resize" {
 bindsym Down resize grow height 10 px
 bindsym Escape mode default
@@ -84,7 +82,6 @@ bindsym k resize shrink height 10 px
 bindsym l resize grow width 10 px
 }
 
-
 bar {
   
   font pango:monospace 8
@@ -110,8 +107,4 @@ bar {
 }
 
 
-
-
-
-
 exec "systemctl --user import-environment; systemctl --user start sway-session.target"
diff --git a/tests/modules/services/window-managers/sway/sway-followmouse-expected.conf b/tests/modules/services/window-managers/sway/sway-followmouse-expected.conf
index 198ce4bd3..931a3ec38 100644
--- a/tests/modules/services/window-managers/sway/sway-followmouse-expected.conf
+++ b/tests/modules/services/window-managers/sway/sway-followmouse-expected.conf
@@ -69,8 +69,6 @@ bindsym Mod1+space focus mode_toggle
 bindsym Mod1+v splitv
 bindsym Mod1+w layout tabbed
 
-
-
 mode "resize" {
 bindsym Down resize grow height 10 px
 bindsym Escape mode default
@@ -85,10 +83,4 @@ bindsym l resize grow width 10 px
 }
 
 
-
-
-
-
-
-
 exec "systemctl --user import-environment; systemctl --user start sway-session.target"
diff --git a/tests/modules/services/window-managers/sway/sway-followmouse-legacy-expected.conf b/tests/modules/services/window-managers/sway/sway-followmouse-legacy-expected.conf
index cbc55722a..3684f1f98 100644
--- a/tests/modules/services/window-managers/sway/sway-followmouse-legacy-expected.conf
+++ b/tests/modules/services/window-managers/sway/sway-followmouse-legacy-expected.conf
@@ -69,8 +69,6 @@ bindsym Mod1+space focus mode_toggle
 bindsym Mod1+v splitv
 bindsym Mod1+w layout tabbed
 
-
-
 mode "resize" {
 bindsym Down resize grow height 10 px
 bindsym Escape mode default
@@ -85,10 +83,4 @@ bindsym l resize grow width 10 px
 }
 
 
-
-
-
-
-
-
 exec "systemctl --user import-environment; systemctl --user start sway-session.target"
diff --git a/tests/modules/services/window-managers/sway/sway-modules.conf b/tests/modules/services/window-managers/sway/sway-modules.conf
new file mode 100644
index 000000000..cba1c628c
--- /dev/null
+++ b/tests/modules/services/window-managers/sway/sway-modules.conf
@@ -0,0 +1,122 @@
+font pango:monospace 8
+floating_modifier Mod1
+default_border pixel 2
+default_floating_border pixel 2
+hide_edge_borders none
+focus_wrapping no
+focus_follows_mouse yes
+focus_on_window_activation smart
+mouse_warping output
+workspace_layout default
+workspace_auto_back_and_forth no
+
+client.focused #4c7899 #285577 #ffffff #2e9ef4 #285577
+client.focused_inactive #333333 #5f676a #ffffff #484e50 #5f676a
+client.unfocused #333333 #222222 #888888 #292d2e #222222
+client.urgent #2f343a #900000 #ffffff #900000 #900000
+client.placeholder #000000 #0c0c0c #ffffff #000000 #0c0c0c
+client.background #ffffff
+
+bindsym Mod1+1 workspace number 1
+bindsym Mod1+2 workspace number 2
+bindsym Mod1+3 workspace number 3
+bindsym Mod1+4 workspace number 4
+bindsym Mod1+5 workspace number 5
+bindsym Mod1+6 workspace number 6
+bindsym Mod1+7 workspace number 7
+bindsym Mod1+8 workspace number 8
+bindsym Mod1+9 workspace number 9
+bindsym Mod1+Down focus down
+bindsym Mod1+Left focus left
+bindsym Mod1+Return exec @rxvt-unicode-unwrapped@/bin/urxvt
+bindsym Mod1+Right focus right
+bindsym Mod1+Shift+1 move container to workspace number 1
+bindsym Mod1+Shift+2 move container to workspace number 2
+bindsym Mod1+Shift+3 move container to workspace number 3
+bindsym Mod1+Shift+4 move container to workspace number 4
+bindsym Mod1+Shift+5 move container to workspace number 5
+bindsym Mod1+Shift+6 move container to workspace number 6
+bindsym Mod1+Shift+7 move container to workspace number 7
+bindsym Mod1+Shift+8 move container to workspace number 8
+bindsym Mod1+Shift+9 move container to workspace number 9
+bindsym Mod1+Shift+Down move down
+bindsym Mod1+Shift+Left move left
+bindsym Mod1+Shift+Right move right
+bindsym Mod1+Shift+Up move up
+bindsym Mod1+Shift+c reload
+bindsym Mod1+Shift+e exec swaynag -t warning -m 'You pressed the exit shortcut. Do you really want to exit sway? This will end your Wayland session.' -b 'Yes, exit sway' 'swaymsg exit'
+bindsym Mod1+Shift+h move left
+bindsym Mod1+Shift+j move down
+bindsym Mod1+Shift+k move up
+bindsym Mod1+Shift+l move right
+bindsym Mod1+Shift+minus move scratchpad
+bindsym Mod1+Shift+q kill
+bindsym Mod1+Shift+space floating toggle
+bindsym Mod1+Up focus up
+bindsym Mod1+a focus parent
+bindsym Mod1+b splith
+bindsym Mod1+d exec @dmenu@/bin/dmenu_run
+bindsym Mod1+e layout toggle split
+bindsym Mod1+f fullscreen toggle
+bindsym Mod1+h focus left
+bindsym Mod1+j focus down
+bindsym Mod1+k focus up
+bindsym Mod1+l focus right
+bindsym Mod1+minus scratchpad show
+bindsym Mod1+r mode resize
+bindsym Mod1+s layout stacking
+bindsym Mod1+space focus mode_toggle
+bindsym Mod1+v splitv
+bindsym Mod1+w layout tabbed
+
+input "*" {
+xkb_variant dvorak
+}
+
+output "HDMI-A-2" {
+bg ~/path/to/background.png fill
+}
+
+seat "*" {
+hide_cursor when-typing enable
+}
+
+mode "resize" {
+bindsym Down resize grow height 10 px
+bindsym Escape mode default
+bindsym Left resize shrink width 10 px
+bindsym Return mode default
+bindsym Right resize grow width 10 px
+bindsym Up resize shrink height 10 px
+bindsym h resize shrink width 10 px
+bindsym j resize grow height 10 px
+bindsym k resize shrink height 10 px
+bindsym l resize grow width 10 px
+}
+
+bar {
+  
+  font pango:monospace 8
+  mode dock
+  hidden_state hide
+  position bottom
+  status_command @i3status@/bin/i3status
+  swaybar_command @sway/bin/swaybar
+  workspace_buttons yes
+  strip_workspace_numbers no
+  tray_output primary
+  colors {
+    background #000000
+    statusline #ffffff
+    separator #666666
+    focused_workspace #4c7899 #285577 #ffffff
+    active_workspace #333333 #5f676a #ffffff
+    inactive_workspace #333333 #222222 #888888
+    urgent_workspace #2f343a #900000 #ffffff
+    binding_mode #2f343a #900000 #ffffff
+  }
+  
+}
+
+
+exec "systemctl --user import-environment; systemctl --user start sway-session.target"
diff --git a/tests/modules/services/window-managers/sway/sway-modules.nix b/tests/modules/services/window-managers/sway/sway-modules.nix
new file mode 100644
index 000000000..bc0df9bda
--- /dev/null
+++ b/tests/modules/services/window-managers/sway/sway-modules.nix
@@ -0,0 +1,42 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  dummy-package = pkgs.runCommandLocal "dummy-package" { } "mkdir $out";
+
+in {
+  config = {
+    wayland.windowManager.sway = {
+      enable = true;
+      package = dummy-package // { outPath = "@sway"; };
+      # overriding findutils causes issues
+      config = {
+        menu = "${pkgs.dmenu}/bin/dmenu_run";
+
+        input = { "*" = { xkb_variant = "dvorak"; }; };
+        output = { "HDMI-A-2" = { bg = "~/path/to/background.png fill"; }; };
+        seat = { "*" = { hide_cursor = "when-typing enable"; }; };
+      };
+    };
+
+    nixpkgs.overlays = [
+      (self: super: {
+        dmenu = dummy-package // { outPath = "@dmenu@"; };
+        rxvt-unicode-unwrapped = dummy-package // {
+          outPath = "@rxvt-unicode-unwrapped@";
+        };
+        i3status = dummy-package // { outPath = "@i3status@"; };
+        sway = dummy-package // { outPath = "@sway@"; };
+        xwayland = dummy-package // { outPath = "@xwayland@"; };
+      })
+    ];
+
+    nmt.script = ''
+      assertFileExists home-files/.config/sway/config
+      assertFileContent home-files/.config/sway/config \
+        ${./sway-modules.conf}
+    '';
+  };
+}
diff --git a/tests/modules/services/window-managers/sway/sway-null-package.conf b/tests/modules/services/window-managers/sway/sway-null-package.conf
index 5d3c5c609..00cefc617 100644
--- a/tests/modules/services/window-managers/sway/sway-null-package.conf
+++ b/tests/modules/services/window-managers/sway/sway-null-package.conf
@@ -69,8 +69,6 @@ bindsym Mod1+space focus mode_toggle
 bindsym Mod1+v splitv
 bindsym Mod1+w layout tabbed
 
-
-
 mode "resize" {
 bindsym Down resize grow height 10 px
 bindsym Escape mode default
@@ -84,7 +82,6 @@ bindsym k resize shrink height 10 px
 bindsym l resize grow width 10 px
 }
 
-
 bar {
   
   font pango:monospace 8
@@ -110,8 +107,4 @@ bar {
 }
 
 
-
-
-
-
 exec "systemctl --user import-environment; systemctl --user start sway-session.target"