Repository: nix-community/stylix
Pull request #1130: treewide: add and partially apply mkTarget function
Author: login: Flameopathic
Labels: name: topic: documentation
description: Documentation additions or improvements
name: topic: home-manager
description: Home Manager target
Created: 2025-04-11T14:15:27Z | Merged: 2025-05-21T14:21:44Z

Changes:
- Adds `mk-target` function in accordance with #1009 
    - Some alterations have been made. See updated docs.
- Uses the function for Hyprland
- Uses the function for Alacritty
<!--
^ Please summarise the changes you have done and explain why they are necessary here ^

Please also link any relevant issues or pull requests e.g. `Closes: #<ISSUE-ID>`
-->

## Things done

<!--
Please check what applies. Note that these are not hard requirements but merely
serve as information for reviewers.
-->
- [ ] Tested locally
- [x] Tested in [testbed](https://stylix.danth.me/testbeds.html)
- [x] Commit message follows [commit convention](https://stylix.danth.me/commit_convention.html)
- [ ] Fits [style guide](https://stylix.danth.me/styling.html)
- [ ] Respects license of any existing code used

## Notify maintainers

<!---
If you are editing an existing target, consider pinging relevant
module maintainers from `modules/<module>/meta.nix`.
-->



Diff:
diff --git a/doc/src/modules.md b/doc/src/modules.md
index c7078888d..ae29e80cb 100644
--- a/doc/src/modules.md
+++ b/doc/src/modules.md
@@ -41,15 +41,34 @@ folder, using any name which is not on the list above.
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`. This is
-similar to
-[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
-from the standard library, however it integrates with
-[`stylix.enable`](./options/nixos.md#stylixenable) and
-[`stylix.autoEnable`](./options/nixos.md#stylixautoenable) and generates more
-specific documentation.
+Modules should be created using the `mkTarget` function whenever possible (see
+the [`/stylix/mk-target.nix`](
+https://github.com/danth/stylix/blob/-/stylix/mk-target.nix) in-source
+documentation for more details):
 
-A general format for modules is shown below.
+```nix
+{ config, lib, mkTarget ... }:
+mkTarget {
+  name = "«name»";
+  humanName = "«human readable name»";
+
+  configElements =
+    { colors }:
+    {
+      programs.«name».theme.background = colors.base00;
+    };
+}
+```
+
+> [!IMPORTANT]
+> The `mkTarget` argument is only available to modules imported by Stylix's
+> [autoload system](https://github.com/danth/stylix/blob/-/stylix/autoload.nix),
+> e.g., `modules/«target»/«platform».nix` modules.
+>
+> I.e., it is not available to normal modules imported via the `imports` list.
+
+When the `mkTarget` function cannot be used, modules must manually replicate its
+safeguarding behaviour:
 
 ```nix
 { config, lib, ... }:
@@ -66,8 +85,8 @@ A general format for modules is shown below.
 ```
 
 > [!CAUTION]
-> You **must** check _both_ `config.stylix.enable` _and_ your target's own
-> `enable` option before defining any config.
+> If not using `mkTarget`, you **must** check _both_ `config.stylix.enable`
+> _and_ your target's own`enable` option before defining any config.
 >
 > In the above example this is done using
 > `config = lib.mkIf (config.stylix.enable && config.stylix.targets.«name».enable)`.
diff --git a/modules/alacritty/hm.nix b/modules/alacritty/hm.nix
index 3c5297221..a39beb08a 100644
--- a/modules/alacritty/hm.nix
+++ b/modules/alacritty/hm.nix
@@ -1,26 +1,16 @@
 # Documentation is available at:
 # - https://alacritty.org/config-alacritty.html
 # - `man 5 alacritty`
-{ config, lib, ... }:
-
-let
-  colors = config.lib.stylix.colors.withHashtag;
-in
-{
-  options.stylix.targets.alacritty.enable =
-    config.lib.stylix.mkEnableTarget "Alacritty" true;
-
-  config =
-    lib.mkIf (config.stylix.enable && config.stylix.targets.alacritty.enable)
+{ config, mkTarget, ... }:
+mkTarget {
+  name = "alacritty";
+  humanName = "Alacritty";
+  configElements = [
+    (
+      { colors }:
+      with colors.withHashtag;
       {
         programs.alacritty.settings = {
-          font = with config.stylix.fonts; {
-            normal = {
-              family = monospace.name;
-              style = "Regular";
-            };
-            size = sizes.terminal;
-          };
           window.opacity = config.stylix.opacity.terminal;
           colors = with colors; {
             primary = {
@@ -60,5 +50,25 @@ in
             };
           };
         };
-      };
+      }
+    )
+    (
+      { fonts }:
+      {
+        programs.alacritty.settings.font = {
+          normal = {
+            family = fonts.monospace.name;
+            style = "Regular";
+          };
+          size = fonts.sizes.terminal;
+        };
+      }
+    )
+    (
+      { opacity }:
+      {
+        programs.alacritty.settings.window.opacity = config.stylix.opacity.terminal;
+      }
+    )
+  ];
 }
diff --git a/modules/hyprland/hm.nix b/modules/hyprland/hm.nix
index 777f04992..b52dda884 100644
--- a/modules/hyprland/hm.nix
+++ b/modules/hyprland/hm.nix
@@ -1,59 +1,52 @@
-{ config, lib, ... }:
-
 {
-  options.stylix.targets.hyprland = {
-    enable = config.lib.stylix.mkEnableTarget "Hyprland" true;
-    hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
-      config.stylix.image != null
-    );
-  };
-
-  config =
-    let
-      cfg = config.stylix.targets.hyprland;
-    in
-    lib.mkIf
-      (
-        config.stylix.enable
-        && cfg.enable
-        && config.wayland.windowManager.hyprland.enable
-      )
-      (
-        lib.mkMerge [
+  config,
+  lib,
+  mkTarget,
+  ...
+}:
+mkTarget {
+  name = "hyprland";
+  humanName = "Hyprland";
+  extraOptions.hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
+    config.stylix.image != null
+  );
+  configElements = [
+    (
+      { colors }:
+      {
+        wayland.windowManager.hyprland.settings =
+          let
+            rgb = color: "rgb(${color})";
+            rgba = color: alpha: "rgba(${color}${alpha})";
+          in
           {
-            wayland.windowManager.hyprland.settings =
-              let
-                inherit (config.lib.stylix) colors;
+            decoration.shadow.color = rgba colors.base00 "99";
+            general = {
+              "col.active_border" = rgb colors.base0D;
+              "col.inactive_border" = rgb colors.base03;
+            };
+            group = {
+              "col.border_inactive" = rgb colors.base03;
+              "col.border_active" = rgb colors.base0D;
+              "col.border_locked_active" = rgb colors.base0C;
 
-                rgb = color: "rgb(${color})";
-                rgba = color: alpha: "rgba(${color}${alpha})";
-              in
-              {
-                decoration.shadow.color = rgba colors.base00 "99";
-                general = {
-                  "col.active_border" = rgb colors.base0D;
-                  "col.inactive_border" = rgb colors.base03;
-                };
-                group = {
-                  "col.border_inactive" = rgb colors.base03;
-                  "col.border_active" = rgb colors.base0D;
-                  "col.border_locked_active" = rgb colors.base0C;
-
-                  groupbar = {
-                    text_color = rgb colors.base05;
-                    "col.active" = rgb colors.base0D;
-                    "col.inactive" = rgb colors.base03;
-                  };
-                };
-                misc.background_color = rgb colors.base00;
+              groupbar = {
+                text_color = rgb colors.base05;
+                "col.active" = rgb colors.base0D;
+                "col.inactive" = rgb colors.base03;
               };
-          }
-
-          (lib.mkIf cfg.hyprpaper.enable {
-            services.hyprpaper.enable = true;
-            stylix.targets.hyprpaper.enable = true;
-            wayland.windowManager.hyprland.settings.misc.disable_hyprland_logo = true;
-          })
-        ]
-      );
+            };
+            misc.background_color = rgb colors.base00;
+          };
+      }
+    )
+    (
+      { cfg }:
+      (lib.mkIf cfg.hyprpaper.enable {
+        services.hyprpaper.enable = true;
+        stylix.targets.hyprpaper.enable = true;
+        wayland.windowManager.hyprland.settings.misc.disable_hyprland_logo = true;
+      })
+    )
+  ];
 }
diff --git a/stylix/autoload.nix b/stylix/autoload.nix
index 98fb8f5d3..5d952e05a 100644
--- a/stylix/autoload.nix
+++ b/stylix/autoload.nix
@@ -8,7 +8,44 @@ builtins.concatLists (
     path: kind:
     let
       file = "${inputs.self}/modules/${path}/${for}.nix";
+      module = import file;
+
+      # Detect whether the file's value has an argument named `mkTarget`
+      useMkTarget =
+        builtins.isFunction module && (builtins.functionArgs module) ? mkTarget;
+
+      # NOTE: `mkTarget` cannot be distributed normally through the module system
+      # due to issues of infinite recursion.
+      mkTarget = import ./mk-target.nix;
     in
-    lib.optional (kind == "directory" && builtins.pathExists file) file
+    lib.optional (kind == "directory" && builtins.pathExists file) (
+      if useMkTarget then
+        { config, ... }@args:
+        let
+          # Based on `lib.modules.applyModuleArgs`
+          #
+          # Apply `mkTarget` as a special arg without actually using `specialArgs`,
+          # which cannot be defined from within a configuration.
+          context =
+            name: ''while evaluating the module argument `${name}' in "${toString file}":'';
+          extraArgs = lib.pipe module [
+            builtins.functionArgs
+            (lib.flip builtins.removeAttrs [ "mkTarget" ])
+            (builtins.mapAttrs (
+              name: _:
+              builtins.addErrorContext (context name) (
+                args.${name} or config._module.args.${name}
+              )
+            ))
+          ];
+        in
+        {
+          key = file;
+          _file = file;
+          imports = [ (module (args // extraArgs // { inherit mkTarget; })) ];
+        }
+      else
+        file
+    )
   ) (builtins.readDir "${inputs.self}/modules")
 )
diff --git a/stylix/mk-target.nix b/stylix/mk-target.nix
new file mode 100644
index 000000000..acd0b5d90
--- /dev/null
+++ b/stylix/mk-target.nix
@@ -0,0 +1,227 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
+  ```
+
+  Where `ModuleBody` is a module that doesn't take any arguments. This allows
+  the caller to use module arguments.
+
+  # Examples
+
+  The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
+  follows:
+
+  ```nix
+  { mkTarget, lib... }:
+  mkTarget {
+    name = "«name»";
+    humanName = "«human readable name»";
+
+    generalConfig =
+      lib.mkIf complexCondition {
+        home.packages = [ pkgs.hello ];
+      };
+
+    configElements = [
+      { programs.«name».theme.name = "stylix"; }
+
+      (
+        { colors }:
+        {
+          programs.«name».theme.background = colors.base00;
+        }
+      )
+
+      (
+        { fonts }:
+        {
+          programs.«name».font.name = fonts.monospace.name;
+        }
+      )
+    ];
+  }
+  ```
+
+  # Inputs
+
+  `config` (Attribute set)
+
+  : `name` (String)
+    : The target name used to generate options in the `stylix.targets.${name}`
+      namespace.
+
+    `humanName` (String)
+    : The descriptive target name passed to the lib.mkEnableOption function
+      when generating the `stylix.targets.${name}.enable` option.
+
+    `autoEnable` (Boolean)
+    : Whether the target should be automatically enabled by default according
+      to the `stylix.autoEnable` option.
+
+      This should be disabled if manual setup is required or if auto-enabling
+      causes issues.
+
+    `extraOptions` (Attribute set)
+    : Additional options to be added in the `stylix.targets.${name}` namespace
+      along the `stylix.targets.${name}.enable` option.
+
+      For example, an extension guard used in the configuration can be declared
+      as follows:
+      ```nix
+      { extension.enable = lib.mkEnableOption "the bloated dependency"; }
+      ```
+
+    `configElements` (List or attribute set or function)
+    : Configuration functions that are automatically safeguarded when any of
+      their arguments is disabled. The provided `cfg` argument conveniently
+      aliases to `config.stylix.targets.${name}`.
+
+      For example, the following configuration is not merged if the stylix
+      colors option is null:
+
+      ```nix
+      (
+        { colors }:
+        {
+          programs.«name».theme.background = colors.base00;
+        }
+      )
+      ```
+
+      The `cfg` alias can be accessed as follows:
+
+      ```nix
+      (
+        { cfg }:
+        {
+          programs.«name».extension.enable = cfg.extension.enable;
+        }
+      )
+      ```
+
+    `generalConfig` (Attribute set or function)
+    : This argument mirrors the `configElements` argument but intentionally
+      lacks automatic safeguarding and should only be used for complex
+      configurations where `configElements` is unsuitable.
+
+  # Environment
+
+  The function is provided alongside module arguments in any modules imported
+  through `/stylix/autoload.nix`.
+*/
+
+# TODO: Ideally, in the future, this function returns an actual module by better
+# integrating with the /stylix/autoload.nix logic, allowing the following target
+# simplification and preventing access to unguarded module arguments by
+# requiring /modules/<MODULE>/<PLATFORM>.nix files to be attribute sets instead
+# of modules:
+#
+#     {
+#       name = "example";
+#       humanName = "Example Target";
+#
+#       generalConfig =
+#         { lib, pkgs }:
+#         lib.mkIf complexCondition {
+#           home.packages = [ pkgs.hello ];
+#         };
+#
+#       configElements = [
+#         { programs.example.theme.name = "stylix"; }
+#
+#         (
+#           { colors }:
+#           {
+#             programs.example.theme.background = colors.base00;
+#           }
+#         )
+#
+#         (
+#           { fonts }:
+#           {
+#             programs.example.font.name = fonts.monospace.name;
+#           }
+#         )
+#       ];
+#     }
+{
+  name,
+  humanName,
+  autoEnable ? true,
+  extraOptions ? { },
+  configElements ? [ ],
+  generalConfig ? null,
+}:
+let
+  module =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+
+      # Get the list of function de-structured argument names.
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+
+      getStylixAttrs =
+        fn:
+        lib.genAttrs (functionArgNames fn) (
+          arg:
+          if arg == "cfg" then
+            cfg
+          else if arg == "colors" then
+            config.lib.stylix.colors
+          else
+            config.stylix.${arg}
+              or (throw "stylix: mkTarget expected one of `cfg`, `colors`, ${
+                lib.concatMapStringsSep ", " (name: "`${name}`") (
+                  builtins.attrNames config.stylix
+                )
+              }, but got: ${arg}")
+        );
+
+      # Call the configuration function with its required Stylix arguments.
+      mkConfig = fn: fn (getStylixAttrs fn);
+
+      # Safeguard configuration functions when any of their arguments is
+      # disabled, while non-function configurations are unguarded.
+      mkConditionalConfig =
+        c:
+        if builtins.isFunction c then
+          let
+            allAttrsNonNull = lib.pipe c [
+              getStylixAttrs
+              builtins.attrValues
+              (builtins.all (attr: attr != null))
+            ];
+          in
+          lib.mkIf allAttrsNonNull (mkConfig c)
+        else
+          c;
+    in
+    {
+      options.stylix.targets.${name}.enable =
+        config.lib.stylix.mkEnableTarget humanName autoEnable;
+
+      config = lib.mkIf (config.stylix.enable && cfg.enable) (
+        lib.mkMerge (
+          lib.optional (generalConfig != null) (mkConfig generalConfig)
+          ++ map mkConditionalConfig (lib.toList configElements)
+        )
+      );
+    };
+in
+{
+  imports = [
+    { options.stylix.targets.${name} = extraOptions; }
+    module
+  ];
+}


Reviews:
user: login: 0xda157
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T18:40:23Z
user: login: trueNAHO
body: > - Adds `lib.stylix.mkTarget` function in accordance with #1009

Very nice to see this become a reality.

> - This is a `lib` function because distributing it through `config` causes infinite recursion
>     - As a side effect, it also must be given `config` to function

Does distributing it through an option work:

```nix
mkTarget = lib.mkOption {
  # TODO: Declare other attributes.

  internal = true;
  readOnly = true;
};
```

IIRC, there have been discussions of migrating `config.lib.stylix` to options, although that is out of the scope of this PR.
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-20T15:10:13Z
user: login: Flameopathic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-20T17:06:14Z
user: login: trueNAHO
body: > As I am writing this, the current implementation is to not distribute it at all and instead define it in the module (`hyprland`) that it is used for purposes of testing its functionality.

The `mkTarget` implementation LGTM, except for two error message nitpicks.

> > > * This is a `lib` function because distributing it through `config` causes infinite recursion
> > >
> > >   * As a side effect, it also must be given `config` to function
> >
> > Does distributing it through an option work:
> >
> > ```nix
> > mkTarget = lib.mkOption {
> >   # TODO: Declare other attributes.
> >
> >   internal = true;
> >   readOnly = true;
> > };
> > ```
>
> I haven't documented this well, but I have tried several methods of distributing it, and none have worked as required just yet.
> - Using `config.lib.stylix.mkTarget` results in infinite recursion
> - Extending `lib` requires using an overlay which is set through `config` and thus results in infinite recursion
>     - Extending `lib` through the flake allows `nix flake check` to pass, but it does not properly extend `lib` when any other system is inputting it. Presumably this is common knowledge, but this is the first time I have tried extending `lib` and thus am mentioning it in case it is either unexpected or I am doing it wrong.
> - I had not tried making it a read-only option; however, it also seems to result in infinite recursion. For context, my implementation used `readOnly = true;` and `default = <<the function>>;`. I then called the function through `config.stylix.mkTarget`.

@danth, @awwpotato, do you have any proposals for distributing `mkTarget`?

state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-29T08:58:37Z
user: login: 0xda157
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-29T23:41:19Z
user: login: Flameopathic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T14:37:08Z
user: login: Flameopathic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T18:18:19Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T18:58:24Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T19:02:42Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T19:05:05Z
user: login: Flameopathic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T19:17:04Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T19:26:05Z
user: login: 0xda157
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T19:43:19Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T19:46:40Z
user: login: Flameopathic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T19:53:43Z
user: login: 0xda157
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T19:54:33Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T19:57:08Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T20:01:32Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T20:07:11Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T20:13:38Z
user: login: 0xda157
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T20:20:14Z
user: login: Flameopathic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T20:22:47Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T20:25:21Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T20:25:50Z
user: login: Flameopathic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T20:42:51Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T20:53:30Z
user: login: Flameopathic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T21:10:15Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T21:22:46Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T21:30:30Z
user: login: Flameopathic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T21:34:43Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T21:59:06Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-05T01:56:33Z
user: login: Flameopathic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T18:35:56Z
user: login: Flameopathic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T18:50:37Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-05T19:06:54Z
user: login: MattSturgeon
body: Looks pretty much there from my perspective. A few very minor ideas, mostly around docs & comments:
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-05T22:40:41Z
user: login: 0xda157
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T23:59:48Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-06T00:14:48Z
user: login: Flameopathic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T12:58:37Z
user: login: Flameopathic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T13:02:05Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-06T13:33:55Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-06T13:35:40Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-06T13:38:24Z
user: login: Flameopathic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T15:32:22Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-06T16:17:56Z
user: login: Flameopathic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T16:22:13Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-06T17:13:50Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-06T21:46:57Z
user: login: MattSturgeon
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-07T19:31:45Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-07T22:40:40Z
user: login: Flameopathic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T23:51:12Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T00:12:43Z
user: login: MattSturgeon
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-08T14:17:43Z
user: login: 0xda157
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-10T23:34:59Z
user: login: MattSturgeon
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-11T18:42:25Z
user: login: trueNAHO
body: This goes without saying, but thanks a lot, @MattSturgeon, for shepherding this PR, as we likely would not have nailed all the details without you.

Here are code suggestions as patches because they are too complex to explain in simple code suggestions in the GitHub UI:

- ````patch
  From fb1b9ba3cf9d6415c55ab84b65663f69c1069ab9 Mon Sep 17 00:00:00 2001
  From: NAHO <90870942+trueNAHO@users.noreply.github.com>
  Date: Tue, 13 May 2025 16:42:11 +0200
  Subject: [PATCH 1/2] doc: centralize mkTarget documentation

  Centralize the mkTarget documentation by merging the
  /docs/src/modules.md and /stylix/mk-target.nix documentation into
  /stylix/mk-target.nix. This makes it easier to read the entire function
  documentation and reduces the chance of documentation drifting.

  The /stylix/mk-target.nix documentation is improved in many ways. The
  new file header briefly explains the function's purpose and demonstrates
  how to use it. The new explanations are more coherent and extensive,
  while leveraging code examples where appropriate.
  ---
   docs/src/modules.md  | 65 ++++++------------------------
   stylix/mk-target.nix | 95 +++++++++++++++++++++++++++++++++++++-------
   2 files changed, 94 insertions(+), 66 deletions(-)

  diff --git a/docs/src/modules.md b/docs/src/modules.md
  index ed7eec2..5f53d6e 100644
  --- a/docs/src/modules.md
  +++ b/docs/src/modules.md
  @@ -34,80 +34,41 @@ Other files needed by the module can also be stored within the

   ## Module template

  -Modules should be created using `mkTarget` whenever possible.
  -
  -When using `mkTarget`, request it _before_ the optional module args.
  -See the below example.
  -
  -When `mkTarget` cannot be used, modules should declare an `enable` option using `mkEnableTarget`
  -and guard their config definitions behind the appropriate `mkIf` conditions.
  -This is similar to
  -[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
  -from the standard library, however it integrates with
  -[`stylix.enable`](./options/nixos.md#stylixenable) and
  -[`stylix.autoEnable`](./options/nixos.md#stylixautoenable)
  -and generates more specific documentation.
  -
  -General formats for modules are shown below.
  -
  -Using `mkTarget`:
  +Modules should be created using the `mkTarget` function whenever possible (see
  +the `/stylix/mk-target.nix` documentation for more details):

   ```nix
   mkTarget:
  -{ config, lib, ... }: # optional module args, after `mkTarget`
  -mkTarget
  -{
  -  name = "«target attribute name»";
  -  humanName = "«human readable name»";
  +{ _config, ... }: # Optional module arguments.
  +mkTarget {
  +  name = "example";
  +  humanName = "Example Target";

     configElements =
       { colors }:
       {
  -      programs.«name».backgroundColor = colors.base00;
  +      programs.example.theme.background = colors.base00;
       };
   }
   ```

  -Manually using `mkEnableTarget`:
  +When the `mkTarget` function cannot be used, modules must manually replicate its
  +safeguarding functionality:

   ```nix
   { config, lib, ... }:
   {
  -  options.stylix.targets.«name».enable =
  -    config.lib.stylix.mkEnableTarget "«human readable name»" true;
  +  options.stylix.targets.example.enable =
  +    config.lib.stylix.mkEnableTarget "Example Target" true;

     config =
  -    lib.mkIf (config.stylix.enable && config.stylix.targets.«name».enable)
  +    lib.mkIf (config.stylix.enable && config.stylix.targets.example.enable)
         {
  -        programs.«name».backgroundColor = config.lib.stylix.colors.base00;
  +        programs.example.theme.background = config.lib.stylix.colors.base00;
         };
   }
   ```

  -### `mkTarget`
  -
  -The `mkTarget` function is a special function which automatically generates
  -options and guards configuration with`stylix.${attr} != null` on configuration
  -to cut down on boilerplate.
  -
  -When using this function, do not access Stylix options through `config` outside
  -of the generated abstractions.
  -
  -Arguments without defaults must be set explicitly.
  -
  -| Argument         | Type                                                      | Default | Description                                                                                                                                                                                                                                                                                                                                                                                                | Example                                                     |
  -| ---------------- | --------------------------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
  -| `name`           | String                                                    |         | Used when generating options; `stylix.targets.${name}`                                                                                                                                                                                                                                                                                                                                                     | "firefox"                                                   |
  -| `humanName`      | String                                                    |         | Used when generating enable option; `stylix.targets.${name}.enable` has a description of "Whether to enable theming for ${humanName}."                                                                                                                                                                                                                                                                     | "Firefox"                                                   |
  -| `autoEnable`     | Boolean                                                   | true    | Whether the target should be auto-enabled. Should be set to `false` if one of the following applies:<ul><li>The module requires further manual setup to work correctly.</li><li>There is no reliable way to detect whether the target is installed, *and* enabling it unconditionally would cause problems.</li></ul>                                                                                      | `false`                                                     |
  -| `extraOptions`   | Attrset of option declarations                            | { }     | Attributes will be mapped to `stylix.targets.${name}.${option}`                                                                                                                                                                                                                                                                                                                                            | `{ colorTheme.enable = lib.mkEnableOption "color theme"; }` |
  -| `configElements` | List of or single (attrset or function returning attrset) | { }     | Function(s) should take the Stylix attributes that it needs (e.g. `colors`, `image`, `fonts`) and `cfg` if necessary. This `cfg` is an alias for `config.stylix.targets.${name}`. Function(s) should return an attribute set to be merged into `config`. If any of the Stylix attributes needed by a/the function are `null`, i.e. if the Stylix option is disabled, the configuration will not be merged. | *See above template*                                        |
  -| `generalConfig`  | Null or function returning attrset                        | null    | Same format as functions under `configElements`. Merged regardless of whether requested Stylix options are `null`. Avoid use when possible.                                                                                                                                                                                                                                                                | *See above template*                                        |
  -
  -If your module will touch options outside of `programs.«name»` or `services.«name»`,
  -it should include config guarded by `mkIf` to prevent any effects when the
  -target is not installed.
  -
   ### Overlays

   If your module is provided as an overlay it uses a special format, where config
  diff --git a/stylix/mk-target.nix b/stylix/mk-target.nix
  index 8742753..f3f7a5e 100644
  --- a/stylix/mk-target.nix
  +++ b/stylix/mk-target.nix
  @@ -1,30 +1,97 @@
  -# See docs/src/modules.md#mkTarget for full explanation
  +# Provides a consistent target interface, minimizing boilerplate and
  +# automatically safeguarding declarations related to disabled options.
  +#
  +# This function returns the module body, not an actual module.
  +#
  +# The /modules/<MODULE>/<PLATFORM>.nix modules should use this function as
  +# follows:
  +#
  +#     mkTarget:
  +#     { _config, ... }: # Optional module arguments.
  +#     mkTarget {
  +#       name = "example";
  +#       humanName = "Example Target";
  +#
  +#       generalConfig =
  +#         { lib, pkgs }:
  +#         lib.mkIf complexCondition {
  +#           home.packages = [ pkgs.hello ];
  +#         };
  +#
  +#       configElements = [
  +#         { programs.example.theme.name = "stylix"; }
  +#
  +#         (
  +#           { colors }:
  +#           {
  +#             programs.example.theme.background = colors.base00;
  +#           }
  +#         )
  +#
  +#         (
  +#           { fonts }:
  +#           {
  +#             programs.example.font.name = fonts.monospace.name;
  +#           }
  +#         )
  +#       ];
  +#     }
  +#
  +# Note 'mkTarget:' preceding the optional module arguments.
   {
  -  # Used when generating options; `stylix.targets.${name}`
  +  # The target name used to generate options in the stylix.targets.${name}
  +  # namespace.
     name,

  -  # Used when generating enable option; `stylix.targets.${name}.enable` has a
  -  # description of "Whether to enable theming for ${humanName}."
  +  # The descriptive target name passed to the lib.mkEnableOption function when
  +  # generating the stylix.targets.${name}.enable option.
     humanName,

  -  # Whether the target should be auto-enabled
  +  # Whether the target should be automatically enabled by default according to
  +  # the stylix.autoEnable option.
  +  #
  +  # This should be disabled if manual setup is required or if auto-enabling
  +  # causes issues.
     autoEnable ? true,

  -  # Attributes will be mapped to `stylix.targets.${name}.${option}`
  +  # Additional options to be added in the stylix.targets.${name} namespace along
  +  # the stylix.targets.${name}.enable option.
  +  #
  +  # For example, an extension guard used in the configuration can be declared as
  +  # follows:
  +  #
  +  #     { extension.enable = lib.mkEnableOption "the bloated dependency"; }
     extraOptions ? { },

  -  # Function(s) should take the Stylix attributes that it needs (e.g. `colors`,
  -  # `image`, `fonts`) and `cfg` if necessary. This `cfg` is an alias for
  -  # `config.stylix.targets.${name}`. Function(s) should return an attribute set
  -  # to be merged into `config`. If any of the Stylix attributes needed by a/the
  -  # function are `null`, AKA unset, the configuration will not be merged.
  +  # Configuration functions that are automatically safeguarded when any of their
  +  # arguments is disabled. The provided 'cfg' argument conveniently aliases to
  +  # config.stylix.targets.${name}.
  +  #
  +  # For example, the following configuration is not merged if the stylix.colors
  +  # option is disabled:
  +  #
  +  #     (
  +  #       { colors }:
  +  #       {
  +  #         programs.example.theme.background = colors.base00;
  +  #       }
  +  #     )
  +  #
  +  # The 'cfg' alias can be accessed as follows:
  +  #
  +  #     (
  +  #       { cfg }:
  +  #       {
  +  #         programs.example.extension.enable = cfg.extension.enable;
  +  #       }
  +  #     )
     configElements ? [ ],

  -  # Same format as functions under `configElements`. Merged regardless of whether
  -  # requested Stylix options are `null`. Avoid use when possible.
  +  # This argument mirrors the configElements argument but intentionally lacks
  +  # automatic safeguarding and should only be used for complex configurations
  +  # where configElements is unsuitable.
     generalConfig ? null,
   }:
  -# Note: we only return a plain module so that calling code can use module args, while using this as their module body
   let
     module =
       { config, lib, ... }:
  --
  2.49.0
  ````

- ````patch
  From 17643cec3f2b48f0a382a77a3fd9920bb74b027e Mon Sep 17 00:00:00 2001
  From: NAHO <90870942+trueNAHO@users.noreply.github.com>
  Date: Tue, 13 May 2025 16:42:17 +0200
  Subject: [PATCH 2/2] doc: document future improvements to mkTarget interface

  Document the future improvements to the mkTarget interface since it was
  deemed out of the scope of this patchset. [2]

  This future improvement may not entirely correspond to awwpotato's
  proposal [1], and is more my own proposal.

  [1]: https://github.com/danth/stylix/pull/1130#discussion_r2072702804
  [2]: https://github.com/danth/stylix/pull/1130#discussion_r2072703719
  ---
   stylix/mk-target.nix | 35 +++++++++++++++++++++++++++++++++++
   1 file changed, 35 insertions(+)

  diff --git a/stylix/mk-target.nix b/stylix/mk-target.nix
  index f3f7a5e..680d342 100644
  --- a/stylix/mk-target.nix
  +++ b/stylix/mk-target.nix
  @@ -38,6 +38,41 @@
   #     }
   #
   # Note 'mkTarget:' preceding the optional module arguments.
  +#
  +# Ideally, in the future, this function returns an actual module by better
  +# integrating with the /stylix/autoload.nix logic, allowing the following target
  +# simplification and preventing access to unguarded module arguments by
  +# requiring /modules/<MODULE>/<PLATFORM>.nix files to be attribute sets instead
  +# of modules:
  +#
  +#     {
  +#       name = "example";
  +#       humanName = "Example Target";
  +#
  +#       generalConfig =
  +#         { lib, pkgs }:
  +#         lib.mkIf complexCondition {
  +#           home.packages = [ pkgs.hello ];
  +#         };
  +#
  +#       configElements = [
  +#         { programs.example.theme.name = "stylix"; }
  +#
  +#         (
  +#           { colors }:
  +#           {
  +#             programs.example.theme.background = colors.base00;
  +#           }
  +#         )
  +#
  +#         (
  +#           { fonts }:
  +#           {
  +#             programs.example.font.name = fonts.monospace.name;
  +#           }
  +#         )
  +#       ];
  +#     }
   {
     # The target name used to generate options in the stylix.targets.${name}
     # namespace.
  --
  2.49.0
  ````

If applying these patches with `git am` after they have been approved is too much trouble, I can `git push` them to this PR if they are approved.

> ```
> Document the future improvements to the mkTarget interface since it was
> deemed out of the scope of this patchset. [2]
>
> This future improvement may not entirely correspond to awwpotato's
> proposal [1], and is more my own proposal.
>
> [1]: https://github.com/danth/stylix/pull/1130#discussion_r2072702804
> [2]: https://github.com/danth/stylix/pull/1130#discussion_r2072703719
> ```

Considering the current commit "stylix: implement `mkTarget`", migrating to my proposal should be possible by changing the `mkTarget` detection to `builtins.isAttrs`:

```diff
-expectsMkTarget =
-  builtins.isFunction module && builtins.functionArgs module == { };
+expectsMkTarget = builtins.isAttrs module;
```

and calling `mkTarget` with the file's attribute set. If this is does not pose too many issues, it might be good to apply this improvement in this patchset.

I suppose this patchset can be merged with the following commit message (adding `Co-authored-by: NAHO <90870942+trueNAHO@users.noreply.github.com>` if one of my patches is applied):

```
treewide: add and apply mkTarget function

Add and apply the mkTarget function to all appropriate modules,
providing a consistent target interface to minimize boilerplate and
automatically safeguard declarations related to disabled options.

The mkTarget function was first discussed in [1] ("extensive mkTarget
function").

[1]: https://github.com/danth/stylix/discussions/1009

Link: https://github.com/danth/stylix/pull/1130

Co-authored-by: Daniel Thwaites <danth@danth.me>
Co-authored-by: Matt Sturgeon <matt@sturgeon.me.uk>
Reviewed-by: Matt Sturgeon <matt@sturgeon.me.uk>
Reviewed-by: awwpotato <awwpotato@voidq.com>
Co-authored-by: NAHO <90870942+trueNAHO@users.noreply.github.com>
Reviewed-by: NAHO <90870942+trueNAHO@users.noreply.github.com>
```

I am fine merging this PR with a merge commit or squash merging with maybe the following commit structure:

- `stylix: add mkTarget function`
- `treewide: apply mkTarget function`
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-13T15:26:02Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T15:49:42Z
user: login: trueNAHO
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T16:41:27Z
user: login: MattSturgeon
body: > This goes without saying, but thanks a lot, @MattSturgeon, for shepherding this PR, as we likely would not have nailed all the details without you.

Thanks for the ACK, it's been an interesting PR to review! All credit goes to @Flameopathic though :grin: 

> Ideally, in the future, this function returns an actual module by better integrating with the `/stylix/autoload.nix` logic, allowing the following target simplification and preventing access to unguarded module arguments by requiring `/modules/<MODULE>/<PLATFORM>.nix` files to be attribute sets instead of modules:

Interesting idea. I've proposed a couple ways we could redesign this below.

While the "file contains `mkTarget` args instead of a module" idea is pretty neat, I think it may be impractical to prevent access to module args.

An alternative idea is to inject `mkTarget` as a special arg. This can't be done through `specialArgs` because that is defined outside the module configuration eval. However you _could_ manually apply module args for any module requesting a `mkTarget` arg...
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T17:21:54Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T17:50:30Z
user: login: Flameopathic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T18:15:54Z
user: login: trueNAHO
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T19:03:45Z
user: login: trueNAHO
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T20:00:42Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T20:19:41Z
user: login: trueNAHO
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T20:30:34Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T20:39:47Z
user: login: Eveeifyeve
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-15T13:59:02Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-15T14:02:55Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T01:55:32Z
user: login: trueNAHO
body: > I think that separating targets entirely from the module system has little benefit and makes this method of configuration even more confusing for new contributors than this function already will make it.

I still think restricting the common case with a simple attribute set is worthwhile:

```nix
# /modules/example/hm.nix
{
  name = /* ... */;
  humanName = /* ... */;
  configElements = /* ... */;
}
```

> I think that distributing the function with module args is the most intuitive way to do it and allows for more creative use of the function than forcing every target to be configured as a simple attribute set. `mkTarget` as it stands may even be able to be used in a map in modules like the Firefox module.
>
> @trueNAHO do you accept distributing `mkTarget` with the module args? If so, I will prettify the commits.

Good point. I did not consider how the `/modules/firefox/hm.nix` module would `map mkTarget targets`. In that case, distributing `mkTarget` as a module argument seems like the best solution. Nonetheless, restricting simple modules not requiring `mkTarget` with an attribute set is still worthwhile.

> I don't know if the treewide reformat should be done in this PR; it will take too long for just me to get all 87 modules reformatted to use `mkTarget`. I think it would be better done in later PRs.

Agreed. Providing `mkTarget` as a module argument allows incremental `mkTarget` migrations.

I am also fine implementing the future attribute set restriction in a follow-up PR, especially since the migration should be trivial:

```diff
-{ mkTarget, ... }:
-mkTarget {
+{
```

In that case, should we bring an adapted version of my initial more elaborate comment:

> ````patch
> From 17643cec3f2b48f0a382a77a3fd9920bb74b027e Mon Sep 17 00:00:00 2001
> From: NAHO <90870942+trueNAHO@users.noreply.github.com>
> Date: Tue, 13 May 2025 16:42:17 +0200
> Subject: [PATCH 2/2] doc: document future improvements to mkTarget interface
>
> Document the future improvements to the mkTarget interface since it was
> deemed out of the scope of this patchset. [2]
>
> This future improvement may not entirely correspond to awwpotato's
> proposal [1], and is more my own proposal.
>
> [1]: https://github.com/danth/stylix/pull/1130#discussion_r2072702804
> [2]: https://github.com/danth/stylix/pull/1130#discussion_r2072703719
> ---
>  stylix/mk-target.nix | 35 +++++++++++++++++++++++++++++++++++
>  1 file changed, 35 insertions(+)
>
> diff --git a/stylix/mk-target.nix b/stylix/mk-target.nix
> index f3f7a5e..680d342 100644
> --- a/stylix/mk-target.nix
> +++ b/stylix/mk-target.nix
> @@ -38,6 +38,41 @@
>  #     }
>  #
>  # Note 'mkTarget:' preceding the optional module arguments.
> +#
> +# Ideally, in the future, this function returns an actual module by better
> +# integrating with the /stylix/autoload.nix logic, allowing the following target
> +# simplification and preventing access to unguarded module arguments by
> +# requiring /modules/<MODULE>/<PLATFORM>.nix files to be attribute sets instead
> +# of modules:
> +#
> +#     {
> +#       name = "example";
> +#       humanName = "Example Target";
> +#
> +#       generalConfig =
> +#         { lib, pkgs }:
> +#         lib.mkIf complexCondition {
> +#           home.packages = [ pkgs.hello ];
> +#         };
> +#
> +#       configElements = [
> +#         { programs.example.theme.name = "stylix"; }
> +#
> +#         (
> +#           { colors }:
> +#           {
> +#             programs.example.theme.background = colors.base00;
> +#           }
> +#         )
> +#
> +#         (
> +#           { fonts }:
> +#           {
> +#             programs.example.font.name = fonts.monospace.name;
> +#           }
> +#         )
> +#       ];
> +#     }
>  {
>    # The target name used to generate options in the stylix.targets.${name}
>    # namespace.
> --
> 2.49.0
> ````
>
> -- https://github.com/danth/stylix/pull/1130#issuecomment-2873909594

state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-16T17:44:28Z
user: login: trueNAHO
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T20:22:13Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T20:57:46Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-17T01:16:42Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-17T01:18:53Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-17T02:22:26Z
user: login: trueNAHO
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-18T16:27:00Z
user: login: trueNAHO
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-18T16:58:39Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-18T19:35:27Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-18T19:38:00Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-18T19:41:30Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T01:25:18Z
user: login: Flameopathic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T01:26:45Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T01:38:00Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T01:45:18Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T02:09:25Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T02:16:34Z
user: login: trueNAHO
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-19T16:37:24Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T17:15:59Z
user: login: MattSturgeon
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T17:18:43Z
user: login: trueNAHO
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T17:40:52Z
user: login: trueNAHO
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T17:46:09Z
user: login: trueNAHO
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T18:07:39Z
user: login: trueNAHO
body: LGTM.

> ```patch
> Subject: [PATCH 2/2] treewide: apply mkTarget
> ```

Should we rename this commit to `treewide: partially apply mkTarget`? In that case, the proposed merge commit message from https://github.com/nix-community/stylix/pull/1130#pullrequestreview-2837078061 should be adapted to:

```
treewide: add and partially apply mkTarget function

Add and partially apply the mkTarget function to all appropriate
modules, providing a consistent target interface to minimize boilerplate
and automatically safeguard declarations related to disabled options.

The mkTarget function was first discussed in [1] ("extensive mkTarget
function").

[1]: https://github.com/danth/stylix/discussions/1009

Link: https://github.com/danth/stylix/pull/1130

Co-authored-by: Daniel Thwaites <danth@danth.me>
Reviewed-by: awwpotato <awwpotato@voidq.com>
Co-authored-by: Matt Sturgeon <matt@sturgeon.me.uk>
Co-authored-by: NAHO <90870942+trueNAHO@users.noreply.github.com>
Reviewed-by: NAHO <90870942+trueNAHO@users.noreply.github.com>
Reviewed-by: Matt Sturgeon <matt@sturgeon.me.uk>
```

@Flameopathic, can you resolve the merge conflicts?

state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-20T16:07:51Z
user: login: MattSturgeon
body: > Should we rename this commit to `treewide: partially apply mkTarget`? In that case, the proposed merge commit message from [#1130 (review)](https://github.com/nix-community/stylix/pull/1130#pullrequestreview-2837078061) should be adapted to:

I like having "add" and "apply" separately, but otherwise agree "partially apply" is more accurate and commits would benefit from having descriptive message bodies.

EDIT: I missed you were talking about a merge-commit not a squashed commit message. That SGTM.

Otherwise, LGTM :rocket: 
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-20T20:03:16Z
user: login: trueNAHO
body: Great to see this get merged :)
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-21T14:21:42Z
user: login: 0xda157
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T15:09:24Z
user: login: 0xda157
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T16:20:31Z
user: login: Flameopathic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T00:40:26Z

Review comments:
diff_hunk: @@ -57,5 +57,78 @@
         // lib.optionalAttrs autoEnable {
           defaultText = lib.literalMD "`stylix.image != null`";
         };
+
+      mkTarget =
+        {
+          name,
+          humanName,
+          autoEnableConditions ? true,
path: stylix/target.nix
user: login: 0xda157
body: ```suggestion
          autoEnable ? true,
```
created_at: 2025-04-11T18:39:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,59 +1,139 @@
 { config, lib, ... }:
-
-{
-  options.stylix.targets.hyprland = {
-    enable = config.lib.stylix.mkEnableTarget "Hyprland" true;
-    hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
-      config.stylix.image != null
-    );
-  };
-
-  config =
+let
+  mkTarget =
+    {
+      name,
+      humanName,
+      autoEnable ? true,
+      extraOptions ? { },
+      configElements ? [ ],
+      generalConfig ? null,
+      config,
+    }:
     let
-      cfg = config.stylix.targets.hyprland;
+      cfg = config.stylix.targets.${name};
     in
-    lib.mkIf
-      (
-        config.stylix.enable
-        && cfg.enable
-        && config.wayland.windowManager.hyprland.enable
-      )
-      (
-        lib.mkMerge [
-          {
-            wayland.windowManager.hyprland.settings =
-              let
-                inherit (config.lib.stylix) colors;
+    {
+      options.stylix.targets.${name} = {
+        enable = config.lib.stylix.mkEnableTarget humanName autoEnable;
+      } // extraOptions;
path: modules/hyprland/hm.nix
user: login: trueNAHO
body: To avoid accidental `extraOptions.enable` overrides, we should `builtins.throw` when `extraOptions ? enable`.

Conveniently, we could use the (arugably overkill) `lib.attrsets.unionOfDisjoint` function since it ensures this and emits a nice error message:

```suggestion
      options.stylix.targets.${name} =
        lib.attrsets.unionOfDisjoint
        {enable = config.lib.stylix.mkEnableTarget humanName autoEnable;}
        extraOptions;
```

created_at: 2025-04-20T13:51:41Z
author_association: MEMBER
diff_hunk: @@ -1,59 +1,139 @@
 { config, lib, ... }:
-
-{
-  options.stylix.targets.hyprland = {
-    enable = config.lib.stylix.mkEnableTarget "Hyprland" true;
-    hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
-      config.stylix.image != null
-    );
-  };
-
-  config =
+let
+  mkTarget =
+    {
+      name,
+      humanName,
+      autoEnable ? true,
+      extraOptions ? { },
+      configElements ? [ ],
+      generalConfig ? null,
+      config,
+    }:
     let
-      cfg = config.stylix.targets.hyprland;
+      cfg = config.stylix.targets.${name};
     in
-    lib.mkIf
-      (
-        config.stylix.enable
-        && cfg.enable
-        && config.wayland.windowManager.hyprland.enable
-      )
-      (
-        lib.mkMerge [
-          {
-            wayland.windowManager.hyprland.settings =
-              let
-                inherit (config.lib.stylix) colors;
+    {
+      options.stylix.targets.${name} = {
+        enable = config.lib.stylix.mkEnableTarget humanName autoEnable;
+      } // extraOptions;
 
-                rgb = color: "rgb(${color})";
-                rgba = color: alpha: "rgba(${color}${alpha})";
-              in
-              {
-                decoration.shadow.color = rgba colors.base00 "99";
-                general = {
-                  "col.active_border" = rgb colors.base0D;
-                  "col.inactive_border" = rgb colors.base03;
-                };
-                group = {
-                  "col.border_inactive" = rgb colors.base03;
-                  "col.border_active" = rgb colors.base0D;
-                  "col.border_locked_active" = rgb colors.base0C;
+      config =
+        let
+          provideStylixArgs =
+            args:
+            (builtins.listToAttrs (
+              map (
+                arg:
+                if arg == "cfg" then
+                  {
+                    name = "cfg";
+                    value = cfg;
+                  }
+                else if arg == "colors" then
+                  {
+                    name = arg;
+                    value = config.lib.stylix.${arg};
+                  }
+                else
+                  {
+                    name = arg;
+                    value = config.stylix.${arg};
+                  }
+              ) args
+            ));
+          provideAvailableStylixArgs =
+            args:
+            (builtins.listToAttrs (
+              map (
+                arg:
+                if arg == "cfg" then
+                  {
+                    name = "cfg";
+                    value = cfg;
+                  }
+                else if (config.stylix.${arg} != null) then
+                  {
+                    name = arg;
+                    value = config.stylix.${arg};
+                  }
+                else
+                  { }
+              ) args
+            ));
+          mkConfig =
+            fn:
+            let
+              args = builtins.attrNames (lib.functionArgs fn);
+            in
+            fn (provideAvailableStylixArgs args);
+          mkConditionalConfig =
+            fn:
+            let
+              args = builtins.attrNames (lib.functionArgs fn);
+            in
+            lib.mkIf (builtins.all (
+              arg:
+              (arg == "cfg")
+              || (
+                if arg == "colors" then
+                  (config.lib.stylix.${arg} != null)
+                else
+                  (config.stylix.${arg} != null)
+              )
+            ) args) (fn (provideStylixArgs args));
+        in
+        lib.mkIf (config.stylix.enable && cfg.enable) (
+          lib.mkMerge (
+            (map mkConditionalConfig configElements)
+            ++ lib.optional (generalConfig != null) (mkConfig generalConfig)
path: modules/hyprland/hm.nix
user: login: trueNAHO
body: Although I doubt this improves performance, it seems better if the unconditional (other than the trivial `generalConfig != null` guard) `generalConfig` is evaluated before the long chain of `configElements`:

```suggestion
            lib.optional (generalConfig != null) (mkConfig generalConfig) ++
            (map mkConditionalConfig configElements)
```

created_at: 2025-04-20T13:58:57Z
author_association: MEMBER
diff_hunk: @@ -1,59 +1,139 @@
 { config, lib, ... }:
-
-{
-  options.stylix.targets.hyprland = {
-    enable = config.lib.stylix.mkEnableTarget "Hyprland" true;
-    hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
-      config.stylix.image != null
-    );
-  };
-
-  config =
+let
+  mkTarget =
+    {
+      name,
+      humanName,
+      autoEnable ? true,
+      extraOptions ? { },
+      configElements ? [ ],
+      generalConfig ? null,
+      config,
+    }:
     let
-      cfg = config.stylix.targets.hyprland;
+      cfg = config.stylix.targets.${name};
     in
-    lib.mkIf
-      (
-        config.stylix.enable
-        && cfg.enable
-        && config.wayland.windowManager.hyprland.enable
-      )
-      (
-        lib.mkMerge [
-          {
-            wayland.windowManager.hyprland.settings =
-              let
-                inherit (config.lib.stylix) colors;
+    {
+      options.stylix.targets.${name} = {
+        enable = config.lib.stylix.mkEnableTarget humanName autoEnable;
+      } // extraOptions;
 
-                rgb = color: "rgb(${color})";
-                rgba = color: alpha: "rgba(${color}${alpha})";
-              in
-              {
-                decoration.shadow.color = rgba colors.base00 "99";
-                general = {
-                  "col.active_border" = rgb colors.base0D;
-                  "col.inactive_border" = rgb colors.base03;
-                };
-                group = {
-                  "col.border_inactive" = rgb colors.base03;
-                  "col.border_active" = rgb colors.base0D;
-                  "col.border_locked_active" = rgb colors.base0C;
+      config =
+        let
+          provideStylixArgs =
+            args:
+            (builtins.listToAttrs (
+              map (
+                arg:
+                if arg == "cfg" then
+                  {
+                    name = "cfg";
+                    value = cfg;
+                  }
+                else if arg == "colors" then
+                  {
+                    name = arg;
+                    value = config.lib.stylix.${arg};
+                  }
+                else
+                  {
+                    name = arg;
+                    value = config.stylix.${arg};
+                  }
+              ) args
+            ));
+          provideAvailableStylixArgs =
+            args:
+            (builtins.listToAttrs (
+              map (
+                arg:
+                if arg == "cfg" then
+                  {
+                    name = "cfg";
+                    value = cfg;
+                  }
+                else if (config.stylix.${arg} != null) then
+                  {
+                    name = arg;
+                    value = config.stylix.${arg};
+                  }
+                else
+                  { }
+              ) args
+            ));
+          mkConfig =
+            fn:
+            let
+              args = builtins.attrNames (lib.functionArgs fn);
+            in
+            fn (provideAvailableStylixArgs args);
+          mkConditionalConfig =
+            fn:
+            let
+              args = builtins.attrNames (lib.functionArgs fn);
+            in
+            lib.mkIf (builtins.all (
+              arg:
+              (arg == "cfg")
+              || (
+                if arg == "colors" then
+                  (config.lib.stylix.${arg} != null)
+                else
+                  (config.stylix.${arg} != null)
+              )
path: modules/hyprland/hm.nix
user: login: trueNAHO
body: Unsure whether the following is not easier to understand, although we are duplicating the `true` case:

```suggestion
              if arg == "cfg" then
                true
              else if arg == "colors" then
                config.lib.stylix.${arg} != null
              else
                config.stylix.${arg} != null;
```

Either way, the following simplification is possible:

```diff
                if arg == "colors" then
-                  (config.lib.stylix.${arg} != null)
+                  config.lib.stylix.${arg} != null
                else
-                  (config.stylix.${arg} != null);
+                  config.stylix.${arg} != null;
```

created_at: 2025-04-20T14:12:21Z
author_association: MEMBER
diff_hunk: @@ -1,59 +1,139 @@
 { config, lib, ... }:
-
-{
-  options.stylix.targets.hyprland = {
-    enable = config.lib.stylix.mkEnableTarget "Hyprland" true;
-    hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
-      config.stylix.image != null
-    );
-  };
-
-  config =
+let
+  mkTarget =
+    {
+      name,
+      humanName,
+      autoEnable ? true,
+      extraOptions ? { },
+      configElements ? [ ],
+      generalConfig ? null,
+      config,
+    }:
     let
-      cfg = config.stylix.targets.hyprland;
+      cfg = config.stylix.targets.${name};
     in
-    lib.mkIf
-      (
-        config.stylix.enable
-        && cfg.enable
-        && config.wayland.windowManager.hyprland.enable
-      )
-      (
-        lib.mkMerge [
-          {
-            wayland.windowManager.hyprland.settings =
-              let
-                inherit (config.lib.stylix) colors;
+    {
+      options.stylix.targets.${name} = {
+        enable = config.lib.stylix.mkEnableTarget humanName autoEnable;
+      } // extraOptions;
 
-                rgb = color: "rgb(${color})";
-                rgba = color: alpha: "rgba(${color}${alpha})";
-              in
-              {
-                decoration.shadow.color = rgba colors.base00 "99";
-                general = {
-                  "col.active_border" = rgb colors.base0D;
-                  "col.inactive_border" = rgb colors.base03;
-                };
-                group = {
-                  "col.border_inactive" = rgb colors.base03;
-                  "col.border_active" = rgb colors.base0D;
-                  "col.border_locked_active" = rgb colors.base0C;
+      config =
+        let
+          provideStylixArgs =
+            args:
+            (builtins.listToAttrs (
+              map (
+                arg:
+                if arg == "cfg" then
+                  {
+                    name = "cfg";
+                    value = cfg;
+                  }
+                else if arg == "colors" then
+                  {
+                    name = arg;
+                    value = config.lib.stylix.${arg};
+                  }
+                else
+                  {
+                    name = arg;
+                    value = config.stylix.${arg};
+                  }
+              ) args
+            ));
+          provideAvailableStylixArgs =
+            args:
+            (builtins.listToAttrs (
+              map (
+                arg:
+                if arg == "cfg" then
+                  {
+                    name = "cfg";
+                    value = cfg;
+                  }
+                else if (config.stylix.${arg} != null) then
+                  {
+                    name = arg;
+                    value = config.stylix.${arg};
+                  }
+                else
+                  { }
+              ) args
+            ));
+          mkConfig =
+            fn:
+            let
+              args = builtins.attrNames (lib.functionArgs fn);
path: modules/hyprland/hm.nix
user: login: trueNAHO
body: I believe the intended behavior is that the `lib.functionArgs fn` arguments should not have any confusing default values, like `{ cfg ? {} }:`. In that case, we should extract

```nix
let
  args = builtins.attrNames (lib.functionArgs fn);
in
```

into a local function

```nix
args = fn: builtins.attrNames (lib.functionArgs fn);
```

that could be used as:

```nix
let
  args' = args fn;
in
```

Then, default argument declarations can be caught with the following nice error message:

```nix
args =
  fn:
  let
    args = lib.functionArgs fn;
    invalidArgs = lib.filterAttrs (_: value: value == true) args;
    invalidArgsString = builtins.concatStringsSep ", " (builtins.attrNames invalidArgs);
  in
  lib.throwIf (invalidArgs != { })
    "function arguments should not have default values: ${invalidArgsString}"
    (builtins.attrNames args);
```

created_at: 2025-04-20T14:20:18Z
author_association: MEMBER
diff_hunk: @@ -1,59 +1,139 @@
 { config, lib, ... }:
-
-{
-  options.stylix.targets.hyprland = {
-    enable = config.lib.stylix.mkEnableTarget "Hyprland" true;
-    hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
-      config.stylix.image != null
-    );
-  };
-
-  config =
+let
+  mkTarget =
+    {
+      name,
+      humanName,
+      autoEnable ? true,
+      extraOptions ? { },
+      configElements ? [ ],
+      generalConfig ? null,
+      config,
+    }:
     let
-      cfg = config.stylix.targets.hyprland;
+      cfg = config.stylix.targets.${name};
     in
-    lib.mkIf
-      (
-        config.stylix.enable
-        && cfg.enable
-        && config.wayland.windowManager.hyprland.enable
-      )
-      (
-        lib.mkMerge [
-          {
-            wayland.windowManager.hyprland.settings =
-              let
-                inherit (config.lib.stylix) colors;
+    {
+      options.stylix.targets.${name} = {
+        enable = config.lib.stylix.mkEnableTarget humanName autoEnable;
+      } // extraOptions;
 
-                rgb = color: "rgb(${color})";
-                rgba = color: alpha: "rgba(${color}${alpha})";
-              in
-              {
-                decoration.shadow.color = rgba colors.base00 "99";
-                general = {
-                  "col.active_border" = rgb colors.base0D;
-                  "col.inactive_border" = rgb colors.base03;
-                };
-                group = {
-                  "col.border_inactive" = rgb colors.base03;
-                  "col.border_active" = rgb colors.base0D;
-                  "col.border_locked_active" = rgb colors.base0C;
+      config =
+        let
+          provideStylixArgs =
+            args:
+            (builtins.listToAttrs (
+              map (
+                arg:
+                if arg == "cfg" then
+                  {
+                    name = "cfg";
+                    value = cfg;
+                  }
+                else if arg == "colors" then
+                  {
+                    name = arg;
+                    value = config.lib.stylix.${arg};
+                  }
+                else
+                  {
+                    name = arg;
+                    value = config.stylix.${arg};
+                  }
+              ) args
+            ));
+          provideAvailableStylixArgs =
+            args:
+            (builtins.listToAttrs (
+              map (
+                arg:
+                if arg == "cfg" then
+                  {
+                    name = "cfg";
+                    value = cfg;
+                  }
+                else if (config.stylix.${arg} != null) then
+                  {
+                    name = arg;
+                    value = config.stylix.${arg};
+                  }
+                else
+                  { }
path: modules/hyprland/hm.nix
user: login: trueNAHO
body: Does this last case not represent requesting non-existent arguments? In that case, it might be better to `builtins.throw` rather than silently ignoring incorrect arguments.

In the `provideStylixArgs` function it would implicitly crash when accessing an invalid attribute in:

> ```nix
>                 else
>                   {
>                     name = arg;
>                     value = config.stylix.${arg};
>                   }
> ```

This might lead to confusing errors. Should we also explicitly `builtins.throw` in this case?

created_at: 2025-04-20T14:59:06Z
author_association: MEMBER
diff_hunk: @@ -1,59 +1,139 @@
 { config, lib, ... }:
-
-{
-  options.stylix.targets.hyprland = {
-    enable = config.lib.stylix.mkEnableTarget "Hyprland" true;
-    hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
-      config.stylix.image != null
-    );
-  };
-
-  config =
+let
+  mkTarget =
+    {
+      name,
+      humanName,
+      autoEnable ? true,
+      extraOptions ? { },
+      configElements ? [ ],
+      generalConfig ? null,
+      config,
+    }:
     let
-      cfg = config.stylix.targets.hyprland;
+      cfg = config.stylix.targets.${name};
     in
-    lib.mkIf
-      (
-        config.stylix.enable
-        && cfg.enable
-        && config.wayland.windowManager.hyprland.enable
-      )
-      (
-        lib.mkMerge [
-          {
-            wayland.windowManager.hyprland.settings =
-              let
-                inherit (config.lib.stylix) colors;
+    {
+      options.stylix.targets.${name} = {
+        enable = config.lib.stylix.mkEnableTarget humanName autoEnable;
+      } // extraOptions;
 
-                rgb = color: "rgb(${color})";
-                rgba = color: alpha: "rgba(${color}${alpha})";
-              in
-              {
-                decoration.shadow.color = rgba colors.base00 "99";
-                general = {
-                  "col.active_border" = rgb colors.base0D;
-                  "col.inactive_border" = rgb colors.base03;
-                };
-                group = {
-                  "col.border_inactive" = rgb colors.base03;
-                  "col.border_active" = rgb colors.base0D;
-                  "col.border_locked_active" = rgb colors.base0C;
+      config =
+        let
+          provideStylixArgs =
+            args:
+            (builtins.listToAttrs (
+              map (
+                arg:
+                if arg == "cfg" then
+                  {
+                    name = "cfg";
+                    value = cfg;
+                  }
+                else if arg == "colors" then
+                  {
+                    name = arg;
+                    value = config.lib.stylix.${arg};
+                  }
+                else
+                  {
+                    name = arg;
+                    value = config.stylix.${arg};
+                  }
+              ) args
+            ));
+          provideAvailableStylixArgs =
+            args:
+            (builtins.listToAttrs (
+              map (
+                arg:
+                if arg == "cfg" then
+                  {
+                    name = "cfg";
+                    value = cfg;
+                  }
+                else if (config.stylix.${arg} != null) then
+                  {
+                    name = arg;
+                    value = config.stylix.${arg};
+                  }
+                else
+                  { }
path: modules/hyprland/hm.nix
user: login: Flameopathic
body: In the `provideAvailableStylixArgs`, it is only supposed to give available arguments and not error for any that are unavailable. I now realize that some of this function is unnecessary complication, so I have revised it.

I have also now added an error message to `provideStylixArgs`
created_at: 2025-04-20T17:06:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,59 +1,152 @@
 { config, lib, ... }:
-
-{
-  options.stylix.targets.hyprland = {
-    enable = config.lib.stylix.mkEnableTarget "Hyprland" true;
-    hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
-      config.stylix.image != null
-    );
-  };
-
-  config =
+let
+  mkTarget =
+    {
+      name,
+      humanName,
+      autoEnable ? true,
+      extraOptions ? { },
+      configElements ? [ ],
+      generalConfig ? null,
+      config,
+    }:
     let
-      cfg = config.stylix.targets.hyprland;
+      cfg = config.stylix.targets.${name};
     in
-    lib.mkIf
-      (
-        config.stylix.enable
-        && cfg.enable
-        && config.wayland.windowManager.hyprland.enable
-      )
-      (
-        lib.mkMerge [
-          {
-            wayland.windowManager.hyprland.settings =
-              let
-                inherit (config.lib.stylix) colors;
+    {
+      options.stylix.targets.${name} = lib.attrsets.unionOfDisjoint {
+        enable = config.lib.stylix.mkEnableTarget humanName autoEnable;
+      } extraOptions;
 
-                rgb = color: "rgb(${color})";
-                rgba = color: alpha: "rgba(${color}${alpha})";
-              in
-              {
-                decoration.shadow.color = rgba colors.base00 "99";
-                general = {
-                  "col.active_border" = rgb colors.base0D;
-                  "col.inactive_border" = rgb colors.base03;
-                };
-                group = {
-                  "col.border_inactive" = rgb colors.base03;
-                  "col.border_active" = rgb colors.base0D;
-                  "col.border_locked_active" = rgb colors.base0C;
+      config =
+        let
+          args =
+            fn:
+            let
+              args = lib.functionArgs fn;
+              invalidArgs = lib.filterAttrs (_: value: value) args;
+              invalidArgsString = builtins.concatStringsSep ", " (
+                builtins.attrNames invalidArgs
+              );
+            in
+            lib.throwIf (invalidArgs != { })
+              "function arguments should not have default values: ${invalidArgsString}"
+              (builtins.attrNames args);
+          provideStylixArgs =
+            args':
+            (builtins.listToAttrs (
+              map (
+                arg:
+                if arg == "cfg" then
+                  {
+                    name = "cfg";
+                    value = cfg;
+                  }
+                else if arg == "colors" then
+                  {
+                    name = arg;
+                    value = config.lib.stylix.${arg};
+                  }
+                else
+                  {
+                    name = arg;
+                    value = config.stylix.${arg};
+                  }
+              ) args'
+            ));
+          provideAvailableStylixArgs =
+            args':
+            (builtins.listToAttrs (
+              map (
+                arg:
+                if arg == "cfg" then
+                  {
+                    name = "cfg";
+                    value = cfg;
+                  }
+                else if arg == "colors" then
+                  {
+                    name = arg;
+                    value = config.lib.stylix.${arg};
+                  }
+                else if config.stylix ? arg then
+                  {
+                    name = arg;
+                    value = config.stylix.${arg};
+                  }
+                else
+                  throw "stylix: Stylix arg was requested by mkTarget but does not exist"
path: modules/hyprland/hm.nix
user: login: trueNAHO
body: ```suggestion
                  throw "stylix: Stylix arg was requested by mkTarget but does not exist: ${arg}"
```
created_at: 2025-04-29T08:44:15Z
author_association: MEMBER
diff_hunk: @@ -1,59 +1,152 @@
 { config, lib, ... }:
-
-{
-  options.stylix.targets.hyprland = {
-    enable = config.lib.stylix.mkEnableTarget "Hyprland" true;
-    hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
-      config.stylix.image != null
-    );
-  };
-
-  config =
+let
+  mkTarget =
+    {
+      name,
+      humanName,
+      autoEnable ? true,
+      extraOptions ? { },
+      configElements ? [ ],
+      generalConfig ? null,
+      config,
+    }:
     let
-      cfg = config.stylix.targets.hyprland;
+      cfg = config.stylix.targets.${name};
     in
-    lib.mkIf
-      (
-        config.stylix.enable
-        && cfg.enable
-        && config.wayland.windowManager.hyprland.enable
-      )
-      (
-        lib.mkMerge [
-          {
-            wayland.windowManager.hyprland.settings =
-              let
-                inherit (config.lib.stylix) colors;
+    {
+      options.stylix.targets.${name} = lib.attrsets.unionOfDisjoint {
+        enable = config.lib.stylix.mkEnableTarget humanName autoEnable;
+      } extraOptions;
 
-                rgb = color: "rgb(${color})";
-                rgba = color: alpha: "rgba(${color}${alpha})";
-              in
-              {
-                decoration.shadow.color = rgba colors.base00 "99";
-                general = {
-                  "col.active_border" = rgb colors.base0D;
-                  "col.inactive_border" = rgb colors.base03;
-                };
-                group = {
-                  "col.border_inactive" = rgb colors.base03;
-                  "col.border_active" = rgb colors.base0D;
-                  "col.border_locked_active" = rgb colors.base0C;
+      config =
+        let
+          args =
+            fn:
+            let
+              args = lib.functionArgs fn;
+              invalidArgs = lib.filterAttrs (_: value: value) args;
+              invalidArgsString = builtins.concatStringsSep ", " (
+                builtins.attrNames invalidArgs
+              );
+            in
+            lib.throwIf (invalidArgs != { })
+              "function arguments should not have default values: ${invalidArgsString}"
+              (builtins.attrNames args);
+          provideStylixArgs =
+            args':
+            (builtins.listToAttrs (
+              map (
+                arg:
+                if arg == "cfg" then
+                  {
+                    name = "cfg";
+                    value = cfg;
+                  }
+                else if arg == "colors" then
+                  {
+                    name = arg;
+                    value = config.lib.stylix.${arg};
+                  }
+                else
+                  {
+                    name = arg;
+                    value = config.stylix.${arg};
+                  }
+              ) args'
+            ));
+          provideAvailableStylixArgs =
+            args':
+            (builtins.listToAttrs (
+              map (
+                arg:
+                if arg == "cfg" then
+                  {
+                    name = "cfg";
+                    value = cfg;
+                  }
+                else if arg == "colors" then
+                  {
+                    name = arg;
+                    value = config.lib.stylix.${arg};
+                  }
+                else if config.stylix ? arg then
+                  {
+                    name = arg;
+                    value = config.stylix.${arg};
+                  }
+                else
+                  throw "stylix: Stylix arg was requested by mkTarget but does not exist"
+              ) args'
+            ));
+          mkConfig = fn: fn (provideAvailableStylixArgs (args fn));
+          mkConditionalConfig =
+            fn:
+            let
+              args' = args fn;
+            in
+            lib.mkIf (builtins.all (
+              arg:
+              if arg == "cfg" then
+                true
+              else if arg == "colors" then
+                config.lib.stylix.${arg} != null
+              else if config.stylix ? arg then
+                config.stylix.${arg} != null
+              else
+                throw "stylix: Stylix arg was requested by mkTarget but does not exist"
path: modules/hyprland/hm.nix
user: login: trueNAHO
body: ```suggestion
                throw "stylix: Stylix arg was requested by mkTarget but does not exist: ${arg}"
```
created_at: 2025-04-29T08:44:45Z
author_association: MEMBER
diff_hunk: @@ -1,59 +1,152 @@
 { config, lib, ... }:
-
-{
-  options.stylix.targets.hyprland = {
-    enable = config.lib.stylix.mkEnableTarget "Hyprland" true;
-    hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
-      config.stylix.image != null
-    );
-  };
-
-  config =
+let
+  mkTarget =
+    {
+      name,
+      humanName,
+      autoEnable ? true,
+      extraOptions ? { },
+      configElements ? [ ],
+      generalConfig ? null,
+      config,
+    }:
     let
-      cfg = config.stylix.targets.hyprland;
+      cfg = config.stylix.targets.${name};
     in
-    lib.mkIf
-      (
-        config.stylix.enable
-        && cfg.enable
-        && config.wayland.windowManager.hyprland.enable
-      )
-      (
-        lib.mkMerge [
-          {
-            wayland.windowManager.hyprland.settings =
-              let
-                inherit (config.lib.stylix) colors;
+    {
+      options.stylix.targets.${name} = lib.attrsets.unionOfDisjoint {
+        enable = config.lib.stylix.mkEnableTarget humanName autoEnable;
+      } extraOptions;
 
-                rgb = color: "rgb(${color})";
-                rgba = color: alpha: "rgba(${color}${alpha})";
-              in
-              {
-                decoration.shadow.color = rgba colors.base00 "99";
-                general = {
-                  "col.active_border" = rgb colors.base0D;
-                  "col.inactive_border" = rgb colors.base03;
-                };
-                group = {
-                  "col.border_inactive" = rgb colors.base03;
-                  "col.border_active" = rgb colors.base0D;
-                  "col.border_locked_active" = rgb colors.base0C;
+      config =
+        let
+          args =
+            fn:
+            let
+              args = lib.functionArgs fn;
+              invalidArgs = lib.filterAttrs (_: value: value) args;
+              invalidArgsString = builtins.concatStringsSep ", " (
+                builtins.attrNames invalidArgs
+              );
+            in
+            lib.throwIf (invalidArgs != { })
+              "function arguments should not have default values: ${invalidArgsString}"
+              (builtins.attrNames args);
+          provideStylixArgs =
+            args':
+            (builtins.listToAttrs (
+              map (
+                arg:
+                if arg == "cfg" then
+                  {
+                    name = "cfg";
+                    value = cfg;
+                  }
+                else if arg == "colors" then
+                  {
+                    name = arg;
+                    value = config.lib.stylix.${arg};
+                  }
+                else
+                  {
+                    name = arg;
+                    value = config.stylix.${arg};
+                  }
+              ) args'
+            ));
+          provideAvailableStylixArgs =
+            args':
+            (builtins.listToAttrs (
+              map (
+                arg:
+                if arg == "cfg" then
+                  {
+                    name = "cfg";
+                    value = cfg;
+                  }
+                else if arg == "colors" then
+                  {
+                    name = arg;
+                    value = config.lib.stylix.${arg};
+                  }
+                else if config.stylix ? arg then
+                  {
+                    name = arg;
+                    value = config.stylix.${arg};
+                  }
+                else
+                  throw "stylix: Stylix arg was requested by mkTarget but does not exist: ${arg}"
+              ) args'
+            ));
+          mkConfig = fn: fn (provideAvailableStylixArgs (args fn));
+          mkConditionalConfig =
+            fn:
+            let
+              args' = args fn;
+            in
+            lib.mkIf (builtins.all (
+              arg:
+              if arg == "cfg" then
+                true
+              else if arg == "colors" then
+                config.lib.stylix.${arg} != null
+              else if config.stylix ? arg then
+                config.stylix.${arg} != null
+              else
+                throw "stylix: Stylix arg was requested by mkTarget but does not exist: ${arg}"
+            ) args') (fn (provideStylixArgs args'));
+        in
+        lib.mkIf (config.stylix.enable && cfg.enable) (
+          lib.mkMerge (
+            lib.optional (generalConfig != null) (mkConfig generalConfig)
+            ++ (map mkConditionalConfig configElements)
+          )
+        );
+    };
+in
+mkTarget {
+  inherit config;
+  name = "hyprland";
+  humanName = "Hyprland";
+  extraOptions.hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
+    config.stylix.image != null
+  );
+  configElements = [
+    (
+      { colors }:
+      {
+        wayland.windowManager.hyprland.settings =
+          let
+            rgb = color: "rgb(${color})";
+            rgba = color: alpha: "rgba(${color}${alpha})";
+          in
+          {
+            decoration.shadow.color = rgba colors.base00 "99";
+            general = {
+              "col.active_border" = rgb colors.base0D;
+              "col.inactive_border" = rgb colors.base03;
+            };
+            group = {
+              "col.border_inactive" = rgb colors.base03;
+              "col.border_active" = rgb colors.base0D;
+              "col.border_locked_active" = rgb colors.base0C;
 
-                  groupbar = {
-                    text_color = rgb colors.base05;
-                    "col.active" = rgb colors.base0D;
-                    "col.inactive" = rgb colors.base03;
-                  };
-                };
-                misc.background_color = rgb colors.base00;
+              groupbar = {
+                text_color = rgb colors.base05;
+                "col.active" = rgb colors.base0D;
+                "col.inactive" = rgb colors.base03;
               };
-          }
-
-          (lib.mkIf cfg.hyprpaper.enable {
-            services.hyprpaper.enable = true;
-            stylix.targets.hyprpaper.enable = true;
-            wayland.windowManager.hyprland.settings.misc.disable_hyprland_logo = true;
-          })
-        ]
-      );
+            };
+            misc.background_color = rgb colors.base00;
+          };
+      }
+    )
+  ];
+  generalConfig =
+    { cfg }:
+    (lib.mkIf cfg.hyprpaper.enable {
+      services.hyprpaper.enable = true;
+      stylix.targets.hyprpaper.enable = true;
+      wayland.windowManager.hyprland.settings.misc.disable_hyprland_logo = true;
+    });
path: modules/hyprland/hm.nix
user: login: 0xda157
body: why can't this just be in configElements?
created_at: 2025-04-29T23:41:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,59 +1,152 @@
 { config, lib, ... }:
-
-{
-  options.stylix.targets.hyprland = {
-    enable = config.lib.stylix.mkEnableTarget "Hyprland" true;
-    hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
-      config.stylix.image != null
-    );
-  };
-
-  config =
+let
+  mkTarget =
+    {
+      name,
+      humanName,
+      autoEnable ? true,
+      extraOptions ? { },
+      configElements ? [ ],
+      generalConfig ? null,
+      config,
+    }:
     let
-      cfg = config.stylix.targets.hyprland;
+      cfg = config.stylix.targets.${name};
     in
-    lib.mkIf
-      (
-        config.stylix.enable
-        && cfg.enable
-        && config.wayland.windowManager.hyprland.enable
-      )
-      (
-        lib.mkMerge [
-          {
-            wayland.windowManager.hyprland.settings =
-              let
-                inherit (config.lib.stylix) colors;
+    {
+      options.stylix.targets.${name} = lib.attrsets.unionOfDisjoint {
+        enable = config.lib.stylix.mkEnableTarget humanName autoEnable;
+      } extraOptions;
 
-                rgb = color: "rgb(${color})";
-                rgba = color: alpha: "rgba(${color}${alpha})";
-              in
-              {
-                decoration.shadow.color = rgba colors.base00 "99";
-                general = {
-                  "col.active_border" = rgb colors.base0D;
-                  "col.inactive_border" = rgb colors.base03;
-                };
-                group = {
-                  "col.border_inactive" = rgb colors.base03;
-                  "col.border_active" = rgb colors.base0D;
-                  "col.border_locked_active" = rgb colors.base0C;
+      config =
+        let
+          args =
+            fn:
+            let
+              args = lib.functionArgs fn;
+              invalidArgs = lib.filterAttrs (_: value: value) args;
+              invalidArgsString = builtins.concatStringsSep ", " (
+                builtins.attrNames invalidArgs
+              );
+            in
+            lib.throwIf (invalidArgs != { })
+              "function arguments should not have default values: ${invalidArgsString}"
+              (builtins.attrNames args);
+          provideStylixArgs =
+            args':
+            (builtins.listToAttrs (
+              map (
+                arg:
+                if arg == "cfg" then
+                  {
+                    name = "cfg";
+                    value = cfg;
+                  }
+                else if arg == "colors" then
+                  {
+                    name = arg;
+                    value = config.lib.stylix.${arg};
+                  }
+                else
+                  {
+                    name = arg;
+                    value = config.stylix.${arg};
+                  }
+              ) args'
+            ));
+          provideAvailableStylixArgs =
+            args':
+            (builtins.listToAttrs (
+              map (
+                arg:
+                if arg == "cfg" then
+                  {
+                    name = "cfg";
+                    value = cfg;
+                  }
+                else if arg == "colors" then
+                  {
+                    name = arg;
+                    value = config.lib.stylix.${arg};
+                  }
+                else if config.stylix ? arg then
+                  {
+                    name = arg;
+                    value = config.stylix.${arg};
+                  }
+                else
+                  throw "stylix: Stylix arg was requested by mkTarget but does not exist: ${arg}"
+              ) args'
+            ));
+          mkConfig = fn: fn (provideAvailableStylixArgs (args fn));
+          mkConditionalConfig =
+            fn:
+            let
+              args' = args fn;
+            in
+            lib.mkIf (builtins.all (
+              arg:
+              if arg == "cfg" then
+                true
+              else if arg == "colors" then
+                config.lib.stylix.${arg} != null
+              else if config.stylix ? arg then
+                config.stylix.${arg} != null
+              else
+                throw "stylix: Stylix arg was requested by mkTarget but does not exist: ${arg}"
+            ) args') (fn (provideStylixArgs args'));
+        in
+        lib.mkIf (config.stylix.enable && cfg.enable) (
+          lib.mkMerge (
+            lib.optional (generalConfig != null) (mkConfig generalConfig)
+            ++ (map mkConditionalConfig configElements)
+          )
+        );
+    };
+in
+mkTarget {
+  inherit config;
+  name = "hyprland";
+  humanName = "Hyprland";
+  extraOptions.hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
+    config.stylix.image != null
+  );
+  configElements = [
+    (
+      { colors }:
+      {
+        wayland.windowManager.hyprland.settings =
+          let
+            rgb = color: "rgb(${color})";
+            rgba = color: alpha: "rgba(${color}${alpha})";
+          in
+          {
+            decoration.shadow.color = rgba colors.base00 "99";
+            general = {
+              "col.active_border" = rgb colors.base0D;
+              "col.inactive_border" = rgb colors.base03;
+            };
+            group = {
+              "col.border_inactive" = rgb colors.base03;
+              "col.border_active" = rgb colors.base0D;
+              "col.border_locked_active" = rgb colors.base0C;
 
-                  groupbar = {
-                    text_color = rgb colors.base05;
-                    "col.active" = rgb colors.base0D;
-                    "col.inactive" = rgb colors.base03;
-                  };
-                };
-                misc.background_color = rgb colors.base00;
+              groupbar = {
+                text_color = rgb colors.base05;
+                "col.active" = rgb colors.base0D;
+                "col.inactive" = rgb colors.base03;
               };
-          }
-
-          (lib.mkIf cfg.hyprpaper.enable {
-            services.hyprpaper.enable = true;
-            stylix.targets.hyprpaper.enable = true;
-            wayland.windowManager.hyprland.settings.misc.disable_hyprland_logo = true;
-          })
-        ]
-      );
+            };
+            misc.background_color = rgb colors.base00;
+          };
+      }
+    )
+  ];
+  generalConfig =
+    { cfg }:
+    (lib.mkIf cfg.hyprpaper.enable {
+      services.hyprpaper.enable = true;
+      stylix.targets.hyprpaper.enable = true;
+      wayland.windowManager.hyprland.settings.misc.disable_hyprland_logo = true;
+    });
path: modules/hyprland/hm.nix
user: login: Flameopathic
body: Good catch, it can. I am going to leave it in `generalConfig` for now for testing purposes.
created_at: 2025-05-04T14:37:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,59 +1,152 @@
 { config, lib, ... }:
-
-{
-  options.stylix.targets.hyprland = {
-    enable = config.lib.stylix.mkEnableTarget "Hyprland" true;
-    hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
-      config.stylix.image != null
-    );
-  };
-
-  config =
+let
+  mkTarget =
+    {
+      name,
+      humanName,
+      autoEnable ? true,
+      extraOptions ? { },
+      configElements ? [ ],
+      generalConfig ? null,
+      config,
+    }:
     let
-      cfg = config.stylix.targets.hyprland;
+      cfg = config.stylix.targets.${name};
     in
-    lib.mkIf
-      (
-        config.stylix.enable
-        && cfg.enable
-        && config.wayland.windowManager.hyprland.enable
-      )
-      (
-        lib.mkMerge [
-          {
-            wayland.windowManager.hyprland.settings =
-              let
-                inherit (config.lib.stylix) colors;
+    {
+      options.stylix.targets.${name} = lib.attrsets.unionOfDisjoint {
+        enable = config.lib.stylix.mkEnableTarget humanName autoEnable;
+      } extraOptions;
 
-                rgb = color: "rgb(${color})";
-                rgba = color: alpha: "rgba(${color}${alpha})";
-              in
-              {
-                decoration.shadow.color = rgba colors.base00 "99";
-                general = {
-                  "col.active_border" = rgb colors.base0D;
-                  "col.inactive_border" = rgb colors.base03;
-                };
-                group = {
-                  "col.border_inactive" = rgb colors.base03;
-                  "col.border_active" = rgb colors.base0D;
-                  "col.border_locked_active" = rgb colors.base0C;
+      config =
+        let
+          args =
+            fn:
+            let
+              args = lib.functionArgs fn;
+              invalidArgs = lib.filterAttrs (_: value: value) args;
+              invalidArgsString = builtins.concatStringsSep ", " (
+                builtins.attrNames invalidArgs
+              );
+            in
+            lib.throwIf (invalidArgs != { })
+              "function arguments should not have default values: ${invalidArgsString}"
+              (builtins.attrNames args);
+          provideStylixArgs =
+            args':
+            (builtins.listToAttrs (
+              map (
+                arg:
+                if arg == "cfg" then
+                  {
+                    name = "cfg";
+                    value = cfg;
+                  }
+                else if arg == "colors" then
+                  {
+                    name = arg;
+                    value = config.lib.stylix.${arg};
+                  }
+                else
+                  {
+                    name = arg;
+                    value = config.stylix.${arg};
+                  }
+              ) args'
+            ));
+          provideAvailableStylixArgs =
+            args':
+            (builtins.listToAttrs (
+              map (
+                arg:
+                if arg == "cfg" then
+                  {
+                    name = "cfg";
+                    value = cfg;
+                  }
+                else if arg == "colors" then
+                  {
+                    name = arg;
+                    value = config.lib.stylix.${arg};
+                  }
+                else if config.stylix ? arg then
+                  {
+                    name = arg;
+                    value = config.stylix.${arg};
+                  }
+                else
+                  throw "stylix: Stylix arg was requested by mkTarget but does not exist: ${arg}"
+              ) args'
+            ));
+          mkConfig = fn: fn (provideAvailableStylixArgs (args fn));
+          mkConditionalConfig =
+            fn:
+            let
+              args' = args fn;
+            in
+            lib.mkIf (builtins.all (
+              arg:
+              if arg == "cfg" then
+                true
+              else if arg == "colors" then
+                config.lib.stylix.${arg} != null
+              else if config.stylix ? arg then
+                config.stylix.${arg} != null
+              else
+                throw "stylix: Stylix arg was requested by mkTarget but does not exist: ${arg}"
+            ) args') (fn (provideStylixArgs args'));
+        in
+        lib.mkIf (config.stylix.enable && cfg.enable) (
+          lib.mkMerge (
+            lib.optional (generalConfig != null) (mkConfig generalConfig)
+            ++ (map mkConditionalConfig configElements)
+          )
+        );
+    };
+in
+mkTarget {
+  inherit config;
+  name = "hyprland";
+  humanName = "Hyprland";
+  extraOptions.hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
+    config.stylix.image != null
+  );
+  configElements = [
+    (
+      { colors }:
+      {
+        wayland.windowManager.hyprland.settings =
+          let
+            rgb = color: "rgb(${color})";
+            rgba = color: alpha: "rgba(${color}${alpha})";
+          in
+          {
+            decoration.shadow.color = rgba colors.base00 "99";
+            general = {
+              "col.active_border" = rgb colors.base0D;
+              "col.inactive_border" = rgb colors.base03;
+            };
+            group = {
+              "col.border_inactive" = rgb colors.base03;
+              "col.border_active" = rgb colors.base0D;
+              "col.border_locked_active" = rgb colors.base0C;
 
-                  groupbar = {
-                    text_color = rgb colors.base05;
-                    "col.active" = rgb colors.base0D;
-                    "col.inactive" = rgb colors.base03;
-                  };
-                };
-                misc.background_color = rgb colors.base00;
+              groupbar = {
+                text_color = rgb colors.base05;
+                "col.active" = rgb colors.base0D;
+                "col.inactive" = rgb colors.base03;
               };
-          }
-
-          (lib.mkIf cfg.hyprpaper.enable {
-            services.hyprpaper.enable = true;
-            stylix.targets.hyprpaper.enable = true;
-            wayland.windowManager.hyprland.settings.misc.disable_hyprland_logo = true;
-          })
-        ]
-      );
+            };
+            misc.background_color = rgb colors.base00;
+          };
+      }
+    )
+  ];
+  generalConfig =
+    { cfg }:
+    (lib.mkIf cfg.hyprpaper.enable {
+      services.hyprpaper.enable = true;
+      stylix.targets.hyprpaper.enable = true;
+      wayland.windowManager.hyprland.settings.misc.disable_hyprland_logo = true;
+    });
path: modules/hyprland/hm.nix
user: login: Flameopathic
body: Moved to `configElements`
created_at: 2025-05-04T18:18:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,59 +1,47 @@
 { config, lib, ... }:
-
-{
-  options.stylix.targets.hyprland = {
-    enable = config.lib.stylix.mkEnableTarget "Hyprland" true;
-    hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
-      config.stylix.image != null
-    );
-  };
-
-  config =
-    let
-      cfg = config.stylix.targets.hyprland;
-    in
-    lib.mkIf
-      (
-        config.stylix.enable
-        && cfg.enable
-        && config.wayland.windowManager.hyprland.enable
-      )
-      (
-        lib.mkMerge [
+lib.modules.importApply ../../stylix/mk-target.nix {
path: modules/hyprland/hm.nix
user: login: MattSturgeon
body: This style of use is bad practice IMO,[^1] because `mk-target.nix` may decide one day it wants to return a full-blown module instead of just a module-body.

---

E.g. if `mk-target.nix` changed to look like:

```nix
{ name, humanName, ... }@args:
{ lib, config, options, ... }@moduleArgs:
{
  # module body
}
```

Then anyone doing:

```nix
{ config, lib, ... }:
import ../../stylix/mk-target.nix {
  # ...
};
```

would find things suddenly break without a clear reason why.

---

Generally, if you're gonna return a module, users of your function should treat it as a full module and import it via `imports`.

If you want to enforce that strictly, you could add module args to mk-target's returned module now. (:+1:)

If you want to stick to the current usage, add a warning note/comment to mk-target.nix to ensure no one accidentally tries to add module args to it in the future. (:-1:)

[^1]: Yes, I know we do _exactly_ this in Nixvim with our `mkVimPlugin` style functions, but that's legacy tech debt from before we knew any better :grin: 
created_at: 2025-05-04T18:35:15Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,102 @@
+{
+  name,
+  humanName,
+  autoEnable ? true,
+  extraOptions ? { },
+  configElements ? [ ],
+  generalConfig ? null,
+}:
+{ config, lib, ... }:
+let
+  cfg = config.stylix.targets.${name};
+in
+{
+  options.stylix.targets.${name} = lib.attrsets.unionOfDisjoint {
+    enable = config.lib.stylix.mkEnableTarget humanName autoEnable;
+  } extraOptions;
path: stylix/mk-target.nix
user: login: MattSturgeon
body: Would it be simpler to just have two modules, instead of doing a `unionOfDisjoint` update here?

```nix
imports = [
  {
    options.stylix.targets.${name}.enable = config.lib.stylix.mkEnableTarget humanName autoEnable;
  }
  {
    options.stylix.targets.${name} = extraOptions;
  }
]
```

The module system will already panic if you try to declare the same option multiple times in a way that the options can't be merged, so no need for `unionOfDisjoint`'s assertions.
created_at: 2025-05-04T18:39:41Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,102 @@
+{
+  name,
+  humanName,
+  autoEnable ? true,
+  extraOptions ? { },
+  configElements ? [ ],
+  generalConfig ? null,
+}:
+{ config, lib, ... }:
+let
+  cfg = config.stylix.targets.${name};
+in
+{
+  options.stylix.targets.${name} = lib.attrsets.unionOfDisjoint {
+    enable = config.lib.stylix.mkEnableTarget humanName autoEnable;
+  } extraOptions;
+
+  config =
+    let
+      args =
+        fn:
+        let
+          args = lib.functionArgs fn;
+          invalidArgs = lib.filterAttrs (_: value: value) args;
+          invalidArgsString = builtins.concatStringsSep ", " (
+            builtins.attrNames invalidArgs
+          );
+        in
+        lib.throwIf (invalidArgs != { })
+          "function arguments should not have default values: ${invalidArgsString}"
+          (builtins.attrNames args);
+      provideStylixArgs =
+        args':
+        (builtins.listToAttrs (
+          map (
+            arg:
+            if arg == "cfg" then
+              {
+                name = "cfg";
+                value = cfg;
+              }
+            else if arg == "colors" then
+              {
+                name = arg;
+                value = config.lib.stylix.${arg};
+              }
+            else
+              {
+                name = arg;
+                value = config.stylix.${arg};
+              }
+          ) args'
+        ));
path: stylix/mk-target.nix
user: login: MattSturgeon
body: This looks very similar to some of the other definitions below, maybe identical?

I believe this could be factored out into some private helper functions defined in a `let in` block somewhere.

Also, nested calls to multiple functions are _sometimes_ neater with `lib.pipe`; worth playing with, but not critical.
created_at: 2025-05-04T18:42:56Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,102 @@
+{
+  name,
+  humanName,
+  autoEnable ? true,
+  extraOptions ? { },
+  configElements ? [ ],
+  generalConfig ? null,
+}:
+{ config, lib, ... }:
+let
+  cfg = config.stylix.targets.${name};
+in
+{
+  options.stylix.targets.${name} = lib.attrsets.unionOfDisjoint {
+    enable = config.lib.stylix.mkEnableTarget humanName autoEnable;
+  } extraOptions;
+
+  config =
+    let
+      args =
+        fn:
+        let
+          args = lib.functionArgs fn;
+          invalidArgs = lib.filterAttrs (_: value: value) args;
path: stylix/mk-target.nix
user: login: MattSturgeon
body: ```suggestion
          invalidArgs = lib.filterAttrs (_: lib.id) args;
```

`x: x` can be replaced with `lib.id`
created_at: 2025-05-04T18:43:25Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,102 @@
+{
+  name,
+  humanName,
+  autoEnable ? true,
+  extraOptions ? { },
+  configElements ? [ ],
+  generalConfig ? null,
+}:
+{ config, lib, ... }:
+let
+  cfg = config.stylix.targets.${name};
+in
+{
+  options.stylix.targets.${name} = lib.attrsets.unionOfDisjoint {
+    enable = config.lib.stylix.mkEnableTarget humanName autoEnable;
+  } extraOptions;
+
+  config =
+    let
+      args =
+        fn:
+        let
+          args = lib.functionArgs fn;
+          invalidArgs = lib.filterAttrs (_: value: value) args;
+          invalidArgsString = builtins.concatStringsSep ", " (
path: stylix/mk-target.nix
user: login: MattSturgeon
body: Not critical, but sometimes it's nice to escape any weirdly formatted names:

```suggestion
          invalidArgsString = builtins.concatMapStringsSep ", " lib.strings.escapeNixIdentifier (
```
created_at: 2025-05-04T18:45:14Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,102 @@
+{
+  name,
+  humanName,
+  autoEnable ? true,
+  extraOptions ? { },
+  configElements ? [ ],
+  generalConfig ? null,
+}:
+{ config, lib, ... }:
+let
+  cfg = config.stylix.targets.${name};
+in
+{
+  options.stylix.targets.${name} = lib.attrsets.unionOfDisjoint {
+    enable = config.lib.stylix.mkEnableTarget humanName autoEnable;
+  } extraOptions;
+
+  config =
+    let
+      args =
path: stylix/mk-target.nix
user: login: MattSturgeon
body: This name is confusing. What does the function do and/or what is the motivation for using it? Maybe answering that will inspire a more descriptive name :crossed_fingers: 

If we can't find a descriptive enough name that the function's purpose is obvious, then maybe a comment is needed too?

The confusion is made worse by there being an `args` variable shadowing this name further down :grin: 
created_at: 2025-05-04T18:49:24Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,102 @@
+{
+  name,
+  humanName,
+  autoEnable ? true,
+  extraOptions ? { },
+  configElements ? [ ],
+  generalConfig ? null,
+}:
+{ config, lib, ... }:
+let
+  cfg = config.stylix.targets.${name};
+in
+{
+  options.stylix.targets.${name} = lib.attrsets.unionOfDisjoint {
+    enable = config.lib.stylix.mkEnableTarget humanName autoEnable;
+  } extraOptions;
+
+  config =
+    let
+      args =
+        fn:
+        let
+          args = lib.functionArgs fn;
+          invalidArgs = lib.filterAttrs (_: value: value) args;
+          invalidArgsString = builtins.concatStringsSep ", " (
+            builtins.attrNames invalidArgs
+          );
+        in
+        lib.throwIf (invalidArgs != { })
+          "function arguments should not have default values: ${invalidArgsString}"
path: stylix/mk-target.nix
user: login: MattSturgeon
body: Is this documented at whatever place the function comes from?
created_at: 2025-05-04T18:50:13Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,102 @@
+{
+  name,
+  humanName,
+  autoEnable ? true,
+  extraOptions ? { },
+  configElements ? [ ],
+  generalConfig ? null,
+}:
+{ config, lib, ... }:
+let
+  cfg = config.stylix.targets.${name};
+in
+{
+  options.stylix.targets.${name} = lib.attrsets.unionOfDisjoint {
+    enable = config.lib.stylix.mkEnableTarget humanName autoEnable;
+  } extraOptions;
+
+  config =
+    let
+      args =
+        fn:
+        let
+          args = lib.functionArgs fn;
+          invalidArgs = lib.filterAttrs (_: value: value) args;
+          invalidArgsString = builtins.concatStringsSep ", " (
+            builtins.attrNames invalidArgs
+          );
+        in
+        lib.throwIf (invalidArgs != { })
+          "function arguments should not have default values: ${invalidArgsString}"
+          (builtins.attrNames args);
+      provideStylixArgs =
+        args':
+        (builtins.listToAttrs (
+          map (
+            arg:
+            if arg == "cfg" then
+              {
+                name = "cfg";
+                value = cfg;
+              }
+            else if arg == "colors" then
+              {
+                name = arg;
+                value = config.lib.stylix.${arg};
+              }
+            else
+              {
+                name = arg;
+                value = config.stylix.${arg};
+              }
+          ) args'
+        ));
+      provideAvailableStylixArgs =
+        args':
+        (builtins.listToAttrs (
+          map (
+            arg:
+            if arg == "cfg" then
+              {
+                name = "cfg";
+                value = cfg;
+              }
+            else if arg == "colors" then
+              {
+                name = arg;
+                value = config.lib.stylix.${arg};
+              }
+            else if config.stylix ? ${arg} then
+              {
+                name = arg;
+                value = config.stylix.${arg};
+              }
+            else
+              throw "stylix: Stylix arg was requested by mkTarget but does not exist: ${arg}"
+          ) args'
+        ));
+      mkConfig = fn: fn (provideAvailableStylixArgs (args fn));
+      mkConditionalConfig =
+        fn:
+        let
+          args' = args fn;
+        in
+        lib.mkIf (builtins.all (
+          arg:
+          if arg == "cfg" then
+            true
+          else if arg == "colors" then
+            config.lib.stylix.${arg} != null
+          else if config.stylix ? ${arg} then
+            config.stylix.${arg} != null
+          else
+            throw "stylix: Stylix arg was requested by mkTarget but does not exist: ${arg}"
+        ) args') (fn (provideStylixArgs args'));
+    in
+    lib.mkIf (config.stylix.enable && cfg.enable) (
+      lib.mkMerge (
+        lib.optional (generalConfig != null) (mkConfig generalConfig)
+        ++ (map mkConditionalConfig configElements)
path: stylix/mk-target.nix
user: login: MattSturgeon
body: Maybe some comments would help remind us at this point what `generalConfig` and `configElements` are and/or why we're using them as config definitions?
created_at: 2025-05-04T18:52:13Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,102 @@
+{
+  name,
+  humanName,
+  autoEnable ? true,
+  extraOptions ? { },
+  configElements ? [ ],
+  generalConfig ? null,
path: stylix/mk-target.nix
user: login: MattSturgeon
body: And/or having some (brief) docs here to clarify what each arg relates to and/or how they should be used?
created_at: 2025-05-04T18:53:28Z
author_association: MEMBER
diff_hunk: @@ -1,59 +1,47 @@
 { config, lib, ... }:
-
-{
-  options.stylix.targets.hyprland = {
-    enable = config.lib.stylix.mkEnableTarget "Hyprland" true;
-    hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
-      config.stylix.image != null
-    );
-  };
-
-  config =
-    let
-      cfg = config.stylix.targets.hyprland;
-    in
-    lib.mkIf
-      (
-        config.stylix.enable
-        && cfg.enable
-        && config.wayland.windowManager.hyprland.enable
-      )
-      (
-        lib.mkMerge [
+lib.modules.importApply ../../stylix/mk-target.nix {
path: modules/hyprland/hm.nix
user: login: MattSturgeon
body: Ah: it looks like you _already do_ return a module-fn with args in `mk-target.nix`, so you should already be running into the issues I described?
created_at: 2025-05-04T18:54:19Z
author_association: MEMBER
diff_hunk: @@ -2,63 +2,71 @@
 # - https://alacritty.org/config-alacritty.html
 # - `man 5 alacritty`
 { config, lib, ... }:
-
-let
-  colors = config.lib.stylix.colors.withHashtag;
-in
-{
-  options.stylix.targets.alacritty.enable =
-    config.lib.stylix.mkEnableTarget "Alacritty" true;
-
-  config =
-    lib.mkIf (config.stylix.enable && config.stylix.targets.alacritty.enable)
+lib.modules.importApply ../../stylix/mk-target.nix {
path: modules/alacritty/hm.nix
user: login: MattSturgeon
body: I'd recommend not using `importApply` for `mk-target`, because I assume we want the target options to show as declared by the calling module (i.e. `modules/alacritty/hm.nix`) _not_ as declared by `stylix/mk-target.nix`.

E.g. using `importApply` would cause most of alacritty's options to show as declared by `stylix/mk-target.nix` in the docs and link to that page instead of to here.
created_at: 2025-05-04T18:56:14Z
author_association: MEMBER
diff_hunk: @@ -1,59 +1,47 @@
 { config, lib, ... }:
-
-{
-  options.stylix.targets.hyprland = {
-    enable = config.lib.stylix.mkEnableTarget "Hyprland" true;
-    hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
-      config.stylix.image != null
-    );
-  };
-
-  config =
-    let
-      cfg = config.stylix.targets.hyprland;
-    in
-    lib.mkIf
-      (
-        config.stylix.enable
-        && cfg.enable
-        && config.wayland.windowManager.hyprland.enable
-      )
-      (
-        lib.mkMerge [
+lib.modules.importApply ../../stylix/mk-target.nix {
path: modules/hyprland/hm.nix
user: login: MattSturgeon
body: > however, using `import ../../stylix/mk-target.nix { /* args */ }` gives the error while `lib.modules.importApply ../../stylix/mk-target.nix { /* args */ }` does not. I am giving the `mk-target` module function one argument while it requests requests two arguments, but I expect that the second (`{ config, lib, ...}`) will be filled by the module system.

This looks like the errors I was predicting.

I'd have to check `importApply`'s impl, but if it returns a "plain" (non-function) wrapper module, _that_ could be why it is helping you avoid the errors I described above.

You can verify this by switching back to `import` **and** dropping line 8 from `mk-target.nix` (and finding another way to pass `config` and `lib` _temporarily_ for test purposes).

This isn't necessarily something I'd choose it for, or a behaviour I'd rely on. 
created_at: 2025-05-04T18:58:11Z
author_association: MEMBER
diff_hunk: @@ -1,59 +1,47 @@
 { config, lib, ... }:
-
-{
-  options.stylix.targets.hyprland = {
-    enable = config.lib.stylix.mkEnableTarget "Hyprland" true;
-    hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
-      config.stylix.image != null
-    );
-  };
-
-  config =
-    let
-      cfg = config.stylix.targets.hyprland;
-    in
-    lib.mkIf
-      (
-        config.stylix.enable
-        && cfg.enable
-        && config.wayland.windowManager.hyprland.enable
-      )
-      (
-        lib.mkMerge [
+lib.modules.importApply ../../stylix/mk-target.nix {
path: modules/hyprland/hm.nix
user: login: MattSturgeon
body: > I am giving the `mk-target` module function one argument while it requests requests two arguments, but I expect that the second (`{ config, lib, ...}`) will be filled by the module system.

Exactly this is the issue.

What's happening here is you have your "caller" module:

```nix
{ config, lib, ... }:
import ../../stylix/mk-target.nix {
  # ...
}
```

Which gets evaluated to:

```nix
{ config, lib, ... }:
{ config, lib, ... }:
{
  # ...
}
```

Using `importApply` _wraps_ the imported module, so you don't notice the issue:

```nix
{ config, lib, ... }:
{
  _file = ../../stylix/mk-target.nix;
  imports = [
    (
      { config, lib, ... }:
      {
        # ...
      }
    )
  ];
}
```
created_at: 2025-05-04T19:02:42Z
author_association: MEMBER
diff_hunk: @@ -2,63 +2,71 @@
 # - https://alacritty.org/config-alacritty.html
 # - `man 5 alacritty`
 { config, lib, ... }:
-
-let
-  colors = config.lib.stylix.colors.withHashtag;
-in
-{
-  options.stylix.targets.alacritty.enable =
-    config.lib.stylix.mkEnableTarget "Alacritty" true;
-
-  config =
-    lib.mkIf (config.stylix.enable && config.stylix.targets.alacritty.enable)
+lib.modules.importApply ../../stylix/mk-target.nix {
path: modules/alacritty/hm.nix
user: login: MattSturgeon
body: (same underlying issue as https://github.com/danth/stylix/pull/1130#discussion_r2072680408)
created_at: 2025-05-04T19:05:05Z
author_association: MEMBER
diff_hunk: @@ -1,59 +1,47 @@
 { config, lib, ... }:
-
-{
-  options.stylix.targets.hyprland = {
-    enable = config.lib.stylix.mkEnableTarget "Hyprland" true;
-    hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
-      config.stylix.image != null
-    );
-  };
-
-  config =
-    let
-      cfg = config.stylix.targets.hyprland;
-    in
-    lib.mkIf
-      (
-        config.stylix.enable
-        && cfg.enable
-        && config.wayland.windowManager.hyprland.enable
-      )
-      (
-        lib.mkMerge [
+lib.modules.importApply ../../stylix/mk-target.nix {
path: modules/hyprland/hm.nix
user: login: Flameopathic
body: I see how this should be causing problems, but the pattern of 
```nix
{ config, ... }:
{
  imports = [
    (import ../../stylix/mk-target.nix {
```
is boilerplate that this function is trying to remove. After improving the function itself from your comments, I will take a look at altering [the function used to import all targets](https://github.com/danth/stylix/blob/master/stylix/autoload.nix) to maybe improve this.
created_at: 2025-05-04T19:17:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,59 +1,47 @@
 { config, lib, ... }:
-
-{
-  options.stylix.targets.hyprland = {
-    enable = config.lib.stylix.mkEnableTarget "Hyprland" true;
-    hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
-      config.stylix.image != null
-    );
-  };
-
-  config =
-    let
-      cfg = config.stylix.targets.hyprland;
-    in
-    lib.mkIf
-      (
-        config.stylix.enable
-        && cfg.enable
-        && config.wayland.windowManager.hyprland.enable
-      )
-      (
-        lib.mkMerge [
+lib.modules.importApply ../../stylix/mk-target.nix {
path: modules/hyprland/hm.nix
user: login: MattSturgeon
body: The alternative if you _really_ want to stick to this usage pattern of providing only a plain module body and not a whole fn-module is to do what nixvim does:

return a plain module, but using `imports` _internally_ in `mk-target.nix` to gain access to args like `lib`, `config`, `options`, etc in an imported module.

This is a little ugly, but it can be made less ugly by breaking things up into separate variable bindings and/or files.

Real world example: here you can see nixvim [define the module](https://github.com/nix-community/nixvim/blob/f7f2dd042f28d235f7bf16cb31dbaee8f35159ca/lib/plugins/mk-vim-plugin.nix#L53-L86) that later [gets imported](https://github.com/nix-community/nixvim/blob/f7f2dd042f28d235f7bf16cb31dbaee8f35159ca/lib/plugins/mk-vim-plugin.nix#L95).

I would still recommend a clarifying comment near the top of `mk-target.nix`, to be explicit about not returning a fn-module and only returning the plain module body, to allow calling-code to take module args if they want to.
created_at: 2025-05-04T19:26:05Z
author_association: MEMBER
diff_hunk: @@ -1,59 +1,47 @@
 { config, lib, ... }:
-
-{
-  options.stylix.targets.hyprland = {
-    enable = config.lib.stylix.mkEnableTarget "Hyprland" true;
-    hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
-      config.stylix.image != null
-    );
-  };
-
-  config =
-    let
-      cfg = config.stylix.targets.hyprland;
-    in
-    lib.mkIf
-      (
-        config.stylix.enable
-        && cfg.enable
-        && config.wayland.windowManager.hyprland.enable
-      )
-      (
-        lib.mkMerge [
+lib.modules.importApply ../../stylix/mk-target.nix {
path: modules/hyprland/hm.nix
user: login: 0xda157
body: also using import to the path of mkTarget is a bad way of going about this, why don't we just put mkTarget under the `config.lib.stylix` namespace?
created_at: 2025-05-04T19:43:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,59 +1,47 @@
 { config, lib, ... }:
-
-{
-  options.stylix.targets.hyprland = {
-    enable = config.lib.stylix.mkEnableTarget "Hyprland" true;
-    hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
-      config.stylix.image != null
-    );
-  };
-
-  config =
-    let
-      cfg = config.stylix.targets.hyprland;
-    in
-    lib.mkIf
-      (
-        config.stylix.enable
-        && cfg.enable
-        && config.wayland.windowManager.hyprland.enable
-      )
-      (
-        lib.mkMerge [
+lib.modules.importApply ../../stylix/mk-target.nix {
path: modules/hyprland/hm.nix
user: login: MattSturgeon
body: > why don't we just put mkTarget under the `config.lib.stylix` namespace?

As laid out in the main comment thread (https://github.com/danth/stylix/pull/1130#issuecomment-2849039039), this is to avoid inf-recursion. Modules cannot be created by something that comes from `config`.

_Maybe_ we'd get away with it here, since it's only the module body, not the full module. But I don't think so.

EDIT: I'm almost certain you can't, because even plain module bodies can contain things like `imports` and would need to be fully resolved before `config` can exist.
created_at: 2025-05-04T19:46:40Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,102 @@
+{
+  name,
+  humanName,
+  autoEnable ? true,
+  extraOptions ? { },
+  configElements ? [ ],
+  generalConfig ? null,
+}:
+{ config, lib, ... }:
+let
+  cfg = config.stylix.targets.${name};
+in
+{
+  options.stylix.targets.${name} = lib.attrsets.unionOfDisjoint {
+    enable = config.lib.stylix.mkEnableTarget humanName autoEnable;
+  } extraOptions;
+
+  config =
+    let
+      args =
+        fn:
+        let
+          args = lib.functionArgs fn;
+          invalidArgs = lib.filterAttrs (_: value: value) args;
+          invalidArgsString = builtins.concatStringsSep ", " (
+            builtins.attrNames invalidArgs
+          );
+        in
+        lib.throwIf (invalidArgs != { })
+          "function arguments should not have default values: ${invalidArgsString}"
path: stylix/mk-target.nix
user: login: Flameopathic
body: It is not; this was a [suggestion](https://github.com/danth/stylix/pull/1130#discussion_r2051734902) that I'm not sure is necessary but applied because it seems harmless. Adding defaults to the function arguments should never have an effect as all pertinent Stylix options have defaults, but it doesn't look good in the code. Honestly having this here is just confusing for reading the function.
created_at: 2025-05-04T19:53:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,59 +1,47 @@
 { config, lib, ... }:
-
-{
-  options.stylix.targets.hyprland = {
-    enable = config.lib.stylix.mkEnableTarget "Hyprland" true;
-    hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
-      config.stylix.image != null
-    );
-  };
-
-  config =
-    let
-      cfg = config.stylix.targets.hyprland;
-    in
-    lib.mkIf
-      (
-        config.stylix.enable
-        && cfg.enable
-        && config.wayland.windowManager.hyprland.enable
-      )
-      (
-        lib.mkMerge [
+lib.modules.importApply ../../stylix/mk-target.nix {
path: modules/hyprland/hm.nix
user: login: 0xda157
body: we could just entirely remove the module system and do something similar to `meta.nix` https://github.com/danth/stylix/blob/master/stylix/meta.nix#L10-L15, but then we would have to allow for a way of writing regular modules for things like `modules/firefox/hm.nix`.
created_at: 2025-05-04T19:54:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,102 @@
+{
+  name,
+  humanName,
+  autoEnable ? true,
+  extraOptions ? { },
+  configElements ? [ ],
+  generalConfig ? null,
+}:
+{ config, lib, ... }:
+let
+  cfg = config.stylix.targets.${name};
+in
+{
+  options.stylix.targets.${name} = lib.attrsets.unionOfDisjoint {
+    enable = config.lib.stylix.mkEnableTarget humanName autoEnable;
+  } extraOptions;
+
+  config =
+    let
+      args =
+        fn:
+        let
+          args = lib.functionArgs fn;
+          invalidArgs = lib.filterAttrs (_: value: value) args;
+          invalidArgsString = builtins.concatStringsSep ", " (
+            builtins.attrNames invalidArgs
+          );
+        in
+        lib.throwIf (invalidArgs != { })
+          "function arguments should not have default values: ${invalidArgsString}"
path: stylix/mk-target.nix
user: login: MattSturgeon
body: Got it. Maybe a brief comment regarding why we're checking them then?
created_at: 2025-05-04T19:57:07Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,80 @@
+{
+  name, # name used for creation of options
+  humanName, # name used for description of options
+  autoEnable ? true, # condition for automatically enabling this target
+  extraOptions ? { }, # options to be added under `options.stylix.targets.${name}`
+  configElements ? [ ], # list of functions
+  generalConfig ? null,
+}:
+{ config, lib, ... }:
+let
+  cfg = config.stylix.targets.${name};
+in
+{
+  imports = [
+    {
+      options.stylix.targets.${name}.enable =
+        config.lib.stylix.mkEnableTarget humanName autoEnable;
+    }
+    {
+      options.stylix.targets.${name} = extraOptions;
+    }
+  ];
+
+  config =
+    let
+      functionArgNames =
+        fn:
+        let
+          args = lib.functionArgs fn;
+          invalidArgs = lib.filterAttrs (_: lib.id) args;
+          invalidArgsString = builtins.concatStringsSep ", " (
+            builtins.attrNames invalidArgs
+          );
+        in
+        lib.throwIf (invalidArgs != { })
+          "function arguments should not have default values: ${invalidArgsString}"
+          (builtins.attrNames args);
+      getStylixAttrs =
+        fn:
+        lib.pipe fn [
+          functionArgNames
+          (map (
+            arg:
+            if arg == "cfg" then
+              {
+                name = "cfg";
+                value = cfg;
+              }
+            else if arg == "colors" then
+              {
+                name = arg;
+                value = config.lib.stylix.${arg};
+              }
+            else if config.stylix ? ${arg} then
+              {
+                name = arg;
+                value = config.stylix.${arg};
+              }
+            else
+              throw "stylix: Stylix arg was requested by mk-target but does not exist: ${arg}"
+          ))
+          builtins.listToAttrs
+        ];
path: stylix/mk-target.nix
user: login: MattSturgeon
body: Maybe I'm missing something, but this looks suspiciously similar to `lib.genAttrs`

```nix
getStylixAttrs =
  fn:
  lib.genAttrs (functionArgNames fn) (
    arg:
    if arg == "cfg" then
      cfg
    else if arg == "colors" then
      config.lib.stylix.${arg}
    else if config.stylix ? ${arg} then
      config.stylix.${arg}
    else
      throw "stylix: Stylix arg was requested by mk-target but does not exist: ${arg}"
  );
```
created_at: 2025-05-04T20:01:31Z
author_association: MEMBER
diff_hunk: @@ -1,59 +1,47 @@
 { config, lib, ... }:
-
-{
-  options.stylix.targets.hyprland = {
-    enable = config.lib.stylix.mkEnableTarget "Hyprland" true;
-    hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
-      config.stylix.image != null
-    );
-  };
-
-  config =
-    let
-      cfg = config.stylix.targets.hyprland;
-    in
-    lib.mkIf
-      (
-        config.stylix.enable
-        && cfg.enable
-        && config.wayland.windowManager.hyprland.enable
-      )
-      (
-        lib.mkMerge [
+lib.modules.importApply ../../stylix/mk-target.nix {
path: modules/hyprland/hm.nix
user: login: MattSturgeon
body: > we could just entirely remove the module system and do something similar to `meta.nix` [`master`/stylix/meta.nix#L10-L15](https://github.com/danth/stylix/blob/master/stylix/meta.nix?rgh-link-date=2025-05-04T19%3A54%3A33Z#L10-L15), but then we would have to allow for a way of writing regular modules for things like `modules/firefox/hm.nix`.

I'm not sure I follow. Isn't the point of this function to create a module to import into a home-manager and/or nixos configuration?

I'm guessing if you're able to "ditch" the module system in other places, its because that stuff isn't intended to end up in actual home-manager/nixos configurations?
created_at: 2025-05-04T20:07:11Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,92 @@
+{
+  # name used for creation of options
+  name,
+
+  # name used for description of options
+  humanName,
+
+  # condition for automatically enabling this target
+  autoEnable ? true,
+
+  # options to be added under `options.stylix.targets.${name}`
+  extraOptions ? { },
+
+  # list of functions with args representing Stylix options; functions
+  # evaluated only if all args present as set attrs
path: stylix/mk-target.nix
user: login: MattSturgeon
body: Awesome, those docs really help clarify things for me as an outsider! Thanks

---

Is this wording nit slightly clearer?

```suggestion
  # are only evaluated if their 1st arg uses de-structured attrs syntax
```
created_at: 2025-05-04T20:13:38Z
author_association: MEMBER
diff_hunk: @@ -1,59 +1,47 @@
 { config, lib, ... }:
-
-{
-  options.stylix.targets.hyprland = {
-    enable = config.lib.stylix.mkEnableTarget "Hyprland" true;
-    hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
-      config.stylix.image != null
-    );
-  };
-
-  config =
-    let
-      cfg = config.stylix.targets.hyprland;
-    in
-    lib.mkIf
-      (
-        config.stylix.enable
-        && cfg.enable
-        && config.wayland.windowManager.hyprland.enable
-      )
-      (
-        lib.mkMerge [
+lib.modules.importApply ../../stylix/mk-target.nix {
path: modules/hyprland/hm.nix
user: login: 0xda157
body: > I'm not sure I follow. Isn't the point of this function to create a module to import into a home-manager and/or nixos configuration?

I'm saying make `modules/*/hm.nix` not a module, and just use `mkTarget` to transform it into a module that gets imported. So we won't include the `{ config, ... }:` at the top of the file and instead just take lib. vague equivalent of what would get exported as the hm module below
```nix
{
  imports = [
    (
      (lib: { 
        name = "alacritty";
        configElements = [
          # ...
        ];
      }) 
      (pkgs.lib.extend (_: _: { stylix = { inherit mkTarget; }; }))
    ) 
  ];
}
```
alternatively we could just use importApply to pass stylixLib
```nix
stylixLib:
{ config, lib, ... }:
stylixLib.mkTarget {
  name = "alacritty";
  configElements = [
    # ...
  ];
}

created_at: 2025-05-04T20:20:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,92 @@
+{
+  # name used for creation of options
+  name,
+
+  # name used for description of options
+  humanName,
+
+  # condition for automatically enabling this target
+  autoEnable ? true,
+
+  # options to be added under `options.stylix.targets.${name}`
+  extraOptions ? { },
+
+  # list of functions with args representing Stylix options; functions
+  # evaluated only if all args present as set attrs
path: stylix/mk-target.nix
user: login: Flameopathic
body: I suppose that there are two things that need to be said here: first and most important is that if any of the requested Stylix attrs are set to `null`, that function's result does not pass a `lib.mkIf`; second is that the function arguments cannot have defaults, which is what I believe you are saying in your suggestion.
created_at: 2025-05-04T20:22:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,59 +1,47 @@
 { config, lib, ... }:
-
-{
-  options.stylix.targets.hyprland = {
-    enable = config.lib.stylix.mkEnableTarget "Hyprland" true;
-    hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
-      config.stylix.image != null
-    );
-  };
-
-  config =
-    let
-      cfg = config.stylix.targets.hyprland;
-    in
-    lib.mkIf
-      (
-        config.stylix.enable
-        && cfg.enable
-        && config.wayland.windowManager.hyprland.enable
-      )
-      (
-        lib.mkMerge [
+lib.modules.importApply ../../stylix/mk-target.nix {
path: modules/hyprland/hm.nix
user: login: MattSturgeon
body: > alternatively we could just use importApply to pass stylixLib

That's an option, but you'd have to do it to all modules that need it.

Regardless, if you want to avoid defining/importing locally your function can't at any point directly depend on `config` or `config.lib.stylix`.

Maybe we should get the function finished/working first, and then look at how best to store it and pass it around in another PR?
created_at: 2025-05-04T20:25:21Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,80 @@
+{
+  name, # name used for creation of options
+  humanName, # name used for description of options
+  autoEnable ? true, # condition for automatically enabling this target
+  extraOptions ? { }, # options to be added under `options.stylix.targets.${name}`
+  configElements ? [ ], # list of functions
+  generalConfig ? null,
+}:
+{ config, lib, ... }:
+let
+  cfg = config.stylix.targets.${name};
+in
+{
+  imports = [
+    {
+      options.stylix.targets.${name}.enable =
+        config.lib.stylix.mkEnableTarget humanName autoEnable;
+    }
+    {
+      options.stylix.targets.${name} = extraOptions;
+    }
+  ];
+
+  config =
+    let
+      functionArgNames =
+        fn:
+        let
+          args = lib.functionArgs fn;
+          invalidArgs = lib.filterAttrs (_: lib.id) args;
+          invalidArgsString = builtins.concatStringsSep ", " (
+            builtins.attrNames invalidArgs
+          );
+        in
+        lib.throwIf (invalidArgs != { })
+          "function arguments should not have default values: ${invalidArgsString}"
+          (builtins.attrNames args);
+      getStylixAttrs =
+        fn:
+        lib.pipe fn [
+          functionArgNames
+          (map (
+            arg:
+            if arg == "cfg" then
+              {
+                name = "cfg";
+                value = cfg;
+              }
+            else if arg == "colors" then
+              {
+                name = arg;
+                value = config.lib.stylix.${arg};
+              }
+            else if config.stylix ? ${arg} then
+              {
+                name = arg;
+                value = config.stylix.${arg};
+              }
+            else
+              throw "stylix: Stylix arg was requested by mk-target but does not exist: ${arg}"
+          ))
+          builtins.listToAttrs
+        ];
path: stylix/mk-target.nix
user: login: MattSturgeon
body: Note 998d275218dce69440bf4e3ccf53d4f5715da090 still has the `{ name, value }` attrs needed for `listToAttrs`. These can go with `genAttrs`, since name is always the string value being mapped and value is always the thing returned by the mapping function.

https://noogle.dev/f/lib/genAttrs
created_at: 2025-05-04T20:25:50Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,92 @@
+{
+  # name used for creation of options
+  name,
+
+  # name used for description of options
+  humanName,
+
+  # condition for automatically enabling this target
+  autoEnable ? true,
+
+  # options to be added under `options.stylix.targets.${name}`
+  extraOptions ? { },
+
+  # list of functions with args representing Stylix options; functions
+  # evaluated only if all args present as set attrs
path: stylix/mk-target.nix
user: login: Flameopathic
body: I have opted just to remove the argument default check as having defaults should make no difference in the current state of the function.
created_at: 2025-05-04T20:42:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,73 @@
+{
+  # name used for creation of options
+  name,
+
+  # name used for description of options
+  humanName,
+
+  # condition for automatically enabling this target
+  autoEnable ? true,
+
+  # options to be added under `options.stylix.targets.${name}`
+  extraOptions ? { },
+
+  # list of functions with args representing Stylix options; function results
+  # only merged if all relevant Stylix options are not null
+  configElements ? [ ],
+
+  # function with args representing Stylix options; function result always
+  # merged
+  generalConfig ? null,
+}:
+{ config, lib, ... }:
+let
+  cfg = config.stylix.targets.${name};
+in
+{
+  imports = [
+    {
+      options.stylix.targets.${name}.enable =
+        config.lib.stylix.mkEnableTarget humanName autoEnable;
+    }
+    {
+      options.stylix.targets.${name} = extraOptions;
+    }
+  ];
+
+  config =
+    let
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+      getStylixAttrs =
+        fn:
+        lib.genAttrs (functionArgNames fn) (
+          arg:
+          if arg == "cfg" then
+            cfg
+          else if arg == "colors" then
+            config.lib.stylix.${arg}
+          else
+            config.stylix.${arg} or throw
+              "stylix: Stylix arg was requested by mk-target but does not exist: ${arg}"
path: stylix/mk-target.nix
user: login: MattSturgeon
body: Believe it or not, this is effectively:

```nix
(config.stylix.${arg} or throw)
    "stylix: Stylix arg was requested by mk-target but does not exist: ${arg}"
```

Right now, so in the happy-path where `${arg}` is present, you will call it like a function, passing it the error string!

(nixfmt is even hinting at this odd [order-of-precedence](https://nix.dev/manual/nix/2.28/language/operators) by formatting the first and second part of the expression on separate lines)

The correct impl is to parenthesize the throw expression:

```nix
config.stylix.${arg} or
    (throw "stylix: Stylix arg was requested by mk-target but does not exist: ${arg}")
```
created_at: 2025-05-04T20:43:49Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,73 @@
+{
+  # name used for creation of options
+  name,
+
+  # name used for description of options
+  humanName,
+
+  # condition for automatically enabling this target
+  autoEnable ? true,
+
+  # options to be added under `options.stylix.targets.${name}`
+  extraOptions ? { },
+
+  # list of functions with args representing Stylix options; function results
+  # only merged if all relevant Stylix options are not null
+  configElements ? [ ],
+
+  # function with args representing Stylix options; function result always
+  # merged
+  generalConfig ? null,
+}:
+{ config, lib, ... }:
+let
+  cfg = config.stylix.targets.${name};
+in
+{
+  imports = [
+    {
+      options.stylix.targets.${name}.enable =
+        config.lib.stylix.mkEnableTarget humanName autoEnable;
+    }
+    {
+      options.stylix.targets.${name} = extraOptions;
+    }
+  ];
+
+  config =
+    let
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+      getStylixAttrs =
+        fn:
+        lib.genAttrs (functionArgNames fn) (
+          arg:
+          if arg == "cfg" then
+            cfg
+          else if arg == "colors" then
+            config.lib.stylix.${arg}
+          else
+            config.stylix.${arg} or throw
+              "stylix: Stylix arg was requested by mk-target but does not exist: ${arg}"
+        );
+      mkConfig = fn: fn (getStylixAttrs fn);
+      mkConditionalConfig =
+        fn:
+        let
+          attrs = functionArgNames fn;
+        in
+        lib.mkIf (builtins.all (
+          attr: if attr == "cfg" then true else attr != null
path: stylix/mk-target.nix
user: login: MattSturgeon
body: I think this is the same:

```suggestion
          attr: attr == "cfg" || attr != null
```

Or maybe even this? :eyes: 

```suggestion
          attr: attr != null
```

Since `attr == "cfg"` is also a non-null attr.
created_at: 2025-05-04T20:46:31Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,73 @@
+{
+  # name used for creation of options
+  name,
+
+  # name used for description of options
+  humanName,
+
+  # condition for automatically enabling this target
+  autoEnable ? true,
+
+  # options to be added under `options.stylix.targets.${name}`
+  extraOptions ? { },
+
+  # list of functions with args representing Stylix options; function results
+  # only merged if all relevant Stylix options are not null
+  configElements ? [ ],
+
+  # function with args representing Stylix options; function result always
+  # merged
+  generalConfig ? null,
+}:
+{ config, lib, ... }:
+let
+  cfg = config.stylix.targets.${name};
+in
path: stylix/mk-target.nix
user: login: MattSturgeon
body: Removing the module args and moving the `config` definition from L37-onwards into an impoprted module would be one way to resolve https://github.com/danth/stylix/pull/1130#discussion_r2072680408:

```suggestion
# Note: we only return a plain module so that calling code can use module args, while using this as their module body
let
  configModule =
    { config, lib, ... }:
    let
      cfg = config.stylix.targets.${name};

      functionArgNames =
        fn:
        lib.pipe fn [
          lib.functionArgs
          builtins.attrNames
        ];

      # ...
    in
    {
      config = lib.mkIf (config.stylix.enable && cfg.enable) (
        lib.mkMerge (
          lib.optional (generalConfig != null) (mkConfig generalConfig)
          ++ map mkConditionalConfig configElements
        )
      );
    };
in
```

```nix
imports = [
  (
    { config, ... }:
    {
      # This could be moved to the above module if prefrred
      # if so, `configModule` may not be the "best" name...
      options.stylix.targets.${name}.enable =
        config.lib.stylix.mkEnableTarget humanName autoEnable;
    }
  )
  {
    options.stylix.targets.${name} = extraOptions;
  }
  configModule
]
```
created_at: 2025-05-04T20:53:12Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,73 @@
+{
+  # name used for creation of options
+  name,
+
+  # name used for description of options
+  humanName,
+
+  # condition for automatically enabling this target
+  autoEnable ? true,
+
+  # options to be added under `options.stylix.targets.${name}`
+  extraOptions ? { },
+
+  # list of functions with args representing Stylix options; function results
+  # only merged if all relevant Stylix options are not null
+  configElements ? [ ],
+
+  # function with args representing Stylix options; function result always
+  # merged
+  generalConfig ? null,
+}:
+{ config, lib, ... }:
+let
+  cfg = config.stylix.targets.${name};
+in
path: stylix/mk-target.nix
user: login: Flameopathic
body: ~~In this case, what would calling the function look like in the context of a module?~~

Sorry, dumb question; writing a full version
created_at: 2025-05-04T21:10:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -9,6 +9,11 @@ builtins.concatLists (
     let
       file = "${inputs.self}/modules/${path}/${for}.nix";
path: stylix/autoload.nix
user: login: MattSturgeon
body: This is a good place to put named variables like: :wink: 

```nix
let
  file = "${inputs.self}/modules/${path}/${for}.nix";
  module = import file;
  # Assume that a function-module whose first arg doesn't have any de-structured attrs is actually expecting `mkTarget` as its first arg
  expectsMkTarget = builtins.isFunction module && builtins.functionArgs module == { };
in
```

The comment & named var are intended to make it clear _why_ we care whether the function-args has any declared de-structured attrs.

Fundamentally, it is a _fairly_ big assumption, so worth making it clear.
created_at: 2025-05-04T21:19:10Z
author_association: MEMBER
diff_hunk: @@ -9,6 +9,11 @@ builtins.concatLists (
     let
       file = "${inputs.self}/modules/${path}/${for}.nix";
     in
-    lib.optional (kind == "directory" && builtins.pathExists file) file
+    lib.optional (kind == "directory" && builtins.pathExists file) (
+      if lib.functionArgs (import file) == { } then
+        lib.modules.importApply file (import ./mk-target.nix)
+      else
+        file
+    )
path: stylix/autoload.nix
user: login: MattSturgeon
body: Nit: could do with some named variables to a) increase readability and b) improve nix eval perf

Bug: you need to check `isFunction` before using `functionArgs`; a plain module would abort with `error: 'functionArgs' requires a function`.

See other comment for ideas: https://github.com/danth/stylix/pull/1130#discussion_r2072716800

It would also be nice to have a re-usable `mkTarget = import ./mk-target.nix` binding somwhere, e.g. the top of the file.
created_at: 2025-05-04T21:21:02Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,77 @@
+{
+  # name used for creation of options
+  name,
+
+  # name used for description of options
+  humanName,
+
+  # condition for automatically enabling this target
+  autoEnable ? true,
+
+  # options to be added under `options.stylix.targets.${name}`
+  extraOptions ? { },
+
+  # list of functions with args representing Stylix options; function results
+  # only merged if all relevant Stylix options are not null
+  configElements ? [ ],
+
+  # function with args representing Stylix options; function result always
+  # merged
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args, while using this as their module body
path: stylix/mk-target.nix
user: login: MattSturgeon
body: :heart_eyes: 
created_at: 2025-05-04T21:21:41Z
author_association: MEMBER
diff_hunk: @@ -9,6 +9,11 @@ builtins.concatLists (
     let
       file = "${inputs.self}/modules/${path}/${for}.nix";
path: stylix/autoload.nix
user: login: MattSturgeon
body: Maybe also worth commenting _why_ we supply the function in this way at all? Someone reading the code will likely wonder why we can't just put it in `config.lib.stylix`. with everything else.
created_at: 2025-05-04T21:30:30Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,77 @@
+{
+  # name used for creation of options
+  name,
+
+  # name used for description of options
+  humanName,
+
+  # condition for automatically enabling this target
+  autoEnable ? true,
+
+  # options to be added under `options.stylix.targets.${name}`
+  extraOptions ? { },
+
+  # list of functions with args representing Stylix options; function results
+  # only merged if all relevant Stylix options are not null
+  configElements ? [ ],
+
+  # function with args representing Stylix options; function result always
+  # merged
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args, while using this as their module body
path: stylix/mk-target.nix
user: login: Flameopathic
body: Is this affirmation for leaving the comment in or a reminder that it should be removed?
created_at: 2025-05-04T21:34:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,77 @@
+{
+  # name used for creation of options
+  name,
+
+  # name used for description of options
+  humanName,
+
+  # condition for automatically enabling this target
+  autoEnable ? true,
+
+  # options to be added under `options.stylix.targets.${name}`
+  extraOptions ? { },
+
+  # list of functions with args representing Stylix options; function results
+  # only merged if all relevant Stylix options are not null
+  configElements ? [ ],
+
+  # function with args representing Stylix options; function result always
+  # merged
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args, while using this as their module body
path: stylix/mk-target.nix
user: login: MattSturgeon
body: It's praise. Sorry for the confusion 🙈
created_at: 2025-05-04T21:59:06Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,77 @@
+{
+  # name used for creation of options
+  name,
+
+  # name used for description of options
+  humanName,
+
+  # condition for automatically enabling this target
+  autoEnable ? true,
+
+  # options to be added under `options.stylix.targets.${name}`
+  extraOptions ? { },
+
+  # list of functions with args representing Stylix options; function results
+  # only merged if all relevant Stylix options are not null
+  configElements ? [ ],
+
+  # function with args representing Stylix options; function result always
+  # merged
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args, while using this as their module body
+let
+  configModule =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+      getStylixAttrs =
+        fn:
+        lib.genAttrs (functionArgNames fn) (
+          arg:
+          if arg == "cfg" then
+            cfg
+          else if arg == "colors" then
+            config.lib.stylix.${arg}
+          else
+            config.stylix.${arg}
+              or (throw "stylix: Stylix arg was requested by mk-target but does not exist: ${arg}")
+        );
+      mkConfig = fn: fn (getStylixAttrs fn);
+      mkConditionalConfig =
+        fn:
+        let
+          attrs = functionArgNames fn;
+        in
+        lib.mkIf (builtins.all (attr: attr != null) attrs) (fn (getStylixAttrs fn));
+    in
+    {
+      config = lib.mkIf (config.stylix.enable && cfg.enable) (
+        lib.mkMerge (
+          lib.optional (generalConfig != null) (mkConfig generalConfig)
+          ++ map mkConditionalConfig configElements
path: stylix/mk-target.nix
user: login: MattSturgeon
body: ```suggestion
          ++ map mkConditionalConfig (lib.toList configElements)
```

If you want to support a single element not needing a list.

created_at: 2025-05-04T22:13:05Z
author_association: MEMBER
diff_hunk: @@ -34,44 +34,47 @@ Other files needed by the module can also be stored within the
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`.
-This is similar to
-[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
-from the standard library, however it integrates with
-[`stylix.enable`](./options/nixos.md#stylixenable) and
-[`stylix.autoEnable`](./options/nixos.md#stylixautoenable)
-and generates more specific documentation.
+All modules should be created using `mkTarget`.
 
 A general format for modules is shown below.
 
 ```nix
+mkTarget:
 { config, lib, ... }:
+mkTarget
 {
-  options.stylix.targets.«name».enable =
-    config.lib.stylix.mkEnableTarget "«human readable name»" true;
+  name = "«target attribute name»";
+  humanName = "«human readable name»";
 
-  config =
-    lib.mkIf (config.stylix.enable && config.stylix.targets.«name».enable)
+  configElements = [
+    { colors }:
       {
         programs.«name».backgroundColor = config.lib.stylix.colors.base00;
       };
+  ];
 }
 ```
 
-The human readable name will be inserted into the following sentence:
+### `mkTarget`
 
-> Whether to enable theming for «human readable name».
+The `mkTarget` function is a special function which automatically generates
+options and guards configuration with`stylix.${attr} != null` on configuration
+to cut down on boilerplate.
 
-If your module will touch options outside of `programs.«name»` or `services.«name»`,
-it should include an additional condition in `mkIf` to prevent any effects
-when the target is not installed.
+Arguments without defaults must be set explicitly.
 
-The boolean value after `mkEnableTarget` should be changed to `false` if
-one of the following applies:
+| Argument | Type | Default | Description | Example |
+|----------|------|---------|-------------|---------|
+|`name`|String||Used when generating options; `stylix.targets.${name}`|"firefox"|
+|`humanName`|String||Used when generating enable option; `stylix.targets.${name}.enable` has a description of "Whether to enable theming for ${humanName}."|"Firefox"|
+|`autoEnableConditions`|Boolean|true|Used for determining when an attribute called `autoEnable` should be set to `false` if one of the following applies:<ul><li>The module requires further manual setup to work correctly.</li><li>There is no reliable way to detect whether the target is installed, *and* enabling it unconditionally would cause problems.</li></ul>|`pkgs.stdenv.hostPlatform.isLinux`|
path: docs/src/modules.md
user: login: MattSturgeon
body: Note: if we're using/recommending dynamic values for `autoEnable` we need to think about how that should be documented. See some of the FIXMEs that came up in #1212, for example.

This is already an issue with `mkEnableTarget`, but previously the recommendation was simply `true` or `false`.

Eventually, we'll probably need some kinda `autoEnableText` that can be incorporated into the relevant options' defaultText & example literalExpressions (or literalMDs).

Maybe for now we should avoid suggesting dynamic values?
created_at: 2025-05-05T01:27:21Z
author_association: MEMBER
diff_hunk: @@ -34,44 +34,47 @@ Other files needed by the module can also be stored within the
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`.
-This is similar to
-[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
-from the standard library, however it integrates with
-[`stylix.enable`](./options/nixos.md#stylixenable) and
-[`stylix.autoEnable`](./options/nixos.md#stylixautoenable)
-and generates more specific documentation.
+All modules should be created using `mkTarget`.
 
 A general format for modules is shown below.
 
 ```nix
+mkTarget:
 { config, lib, ... }:
+mkTarget
 {
-  options.stylix.targets.«name».enable =
-    config.lib.stylix.mkEnableTarget "«human readable name»" true;
+  name = "«target attribute name»";
+  humanName = "«human readable name»";
 
-  config =
-    lib.mkIf (config.stylix.enable && config.stylix.targets.«name».enable)
+  configElements = [
+    { colors }:
       {
         programs.«name».backgroundColor = config.lib.stylix.colors.base00;
       };
+  ];
 }
 ```
 
-The human readable name will be inserted into the following sentence:
+### `mkTarget`
 
-> Whether to enable theming for «human readable name».
+The `mkTarget` function is a special function which automatically generates
+options and guards configuration with`stylix.${attr} != null` on configuration
+to cut down on boilerplate.
 
-If your module will touch options outside of `programs.«name»` or `services.«name»`,
-it should include an additional condition in `mkIf` to prevent any effects
-when the target is not installed.
+Arguments without defaults must be set explicitly.
 
-The boolean value after `mkEnableTarget` should be changed to `false` if
-one of the following applies:
+| Argument | Type | Default | Description | Example |
+|----------|------|---------|-------------|---------|
+|`name`|String||Used when generating options; `stylix.targets.${name}`|"firefox"|
+|`humanName`|String||Used when generating enable option; `stylix.targets.${name}.enable` has a description of "Whether to enable theming for ${humanName}."|"Firefox"|
+|`autoEnableConditions`|Boolean|true|Used for determining when an attribute called `autoEnable` should be set to `false` if one of the following applies:<ul><li>The module requires further manual setup to work correctly.</li><li>There is no reliable way to detect whether the target is installed, *and* enabling it unconditionally would cause problems.</li></ul>|`pkgs.stdenv.hostPlatform.isLinux`|
+|`extraOptions`|Attrset of options|{ }|Attributes will be mapped to `stylix.targets.${name}.${option}`|`{ colorTheme.enable = lib.mkEnableOption "color theme"; }`|
+|`configElements`|List of function returning attrset|{ }|Each function should take the Stylix attributes that it needs (e.g. `colors`, `image`, `fonts`) and `cfg` if necessary. This `cfg` is an alias for `config.stylix.targets.${name}`. Each function should return an attribute set to be merged into `config`. If any of the Stylix attributes needed by a function are `null`, the configuration will not be merged.|*See above template*|
path: docs/src/modules.md
user: login: MattSturgeon
body: Nice to haves:

It being a list could be optional; we could normalise internally with `lib.toList`

Elememts being a function could be optional; we could check using `isFunction` and/or `isAttrs`.
created_at: 2025-05-05T01:30:15Z
author_association: MEMBER
diff_hunk: @@ -34,44 +34,47 @@ Other files needed by the module can also be stored within the
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`.
-This is similar to
-[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
-from the standard library, however it integrates with
-[`stylix.enable`](./options/nixos.md#stylixenable) and
-[`stylix.autoEnable`](./options/nixos.md#stylixautoenable)
-and generates more specific documentation.
+All modules should be created using `mkTarget`.
 
 A general format for modules is shown below.
 
 ```nix
+mkTarget:
 { config, lib, ... }:
+mkTarget
 {
-  options.stylix.targets.«name».enable =
-    config.lib.stylix.mkEnableTarget "«human readable name»" true;
+  name = "«target attribute name»";
+  humanName = "«human readable name»";
 
-  config =
-    lib.mkIf (config.stylix.enable && config.stylix.targets.«name».enable)
+  configElements = [
+    { colors }:
       {
         programs.«name».backgroundColor = config.lib.stylix.colors.base00;
       };
+  ];
 }
 ```
 
-The human readable name will be inserted into the following sentence:
+### `mkTarget`
 
-> Whether to enable theming for «human readable name».
+The `mkTarget` function is a special function which automatically generates
+options and guards configuration with`stylix.${attr} != null` on configuration
+to cut down on boilerplate.
 
-If your module will touch options outside of `programs.«name»` or `services.«name»`,
-it should include an additional condition in `mkIf` to prevent any effects
-when the target is not installed.
+Arguments without defaults must be set explicitly.
 
-The boolean value after `mkEnableTarget` should be changed to `false` if
-one of the following applies:
+| Argument | Type | Default | Description | Example |
+|----------|------|---------|-------------|---------|
+|`name`|String||Used when generating options; `stylix.targets.${name}`|"firefox"|
+|`humanName`|String||Used when generating enable option; `stylix.targets.${name}.enable` has a description of "Whether to enable theming for ${humanName}."|"Firefox"|
+|`autoEnableConditions`|Boolean|true|Used for determining when an attribute called `autoEnable` should be set to `false` if one of the following applies:<ul><li>The module requires further manual setup to work correctly.</li><li>There is no reliable way to detect whether the target is installed, *and* enabling it unconditionally would cause problems.</li></ul>|`pkgs.stdenv.hostPlatform.isLinux`|
+|`extraOptions`|Attrset of options|{ }|Attributes will be mapped to `stylix.targets.${name}.${option}`|`{ colorTheme.enable = lib.mkEnableOption "color theme"; }`|
+|`configElements`|List of function returning attrset|{ }|Each function should take the Stylix attributes that it needs (e.g. `colors`, `image`, `fonts`) and `cfg` if necessary. This `cfg` is an alias for `config.stylix.targets.${name}`. Each function should return an attribute set to be merged into `config`. If any of the Stylix attributes needed by a function are `null`, the configuration will not be merged.|*See above template*|
+|`generalConfig`|Null or function returning attrset|null|Same function format as in `configElements`, except the result of this function will always be merged with `config`. Avoid use when possible.|*See above template*|
path: docs/src/modules.md
user: login: MattSturgeon
body: Same: could be null or attrs or function.

Also: I wonder if the wording could be simplified? Maybe something along the lines of "unconditional config" that is "always defined" "regardless of enable options" 🤔 ?
created_at: 2025-05-05T01:33:50Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,77 @@
+{
+  # name used for creation of options
+  name,
+
+  # name used for description of options
+  humanName,
+
+  # condition for automatically enabling this target
+  autoEnable ? true,
+
+  # options to be added under `options.stylix.targets.${name}`
+  extraOptions ? { },
+
+  # list of functions with args representing Stylix options; function results
+  # only merged if all relevant Stylix options are not null
+  configElements ? [ ],
+
+  # function with args representing Stylix options; function result always
+  # merged
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args, while using this as their module body
+let
+  configModule =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+      getStylixAttrs =
+        fn:
+        lib.genAttrs (functionArgNames fn) (
+          arg:
+          if arg == "cfg" then
+            cfg
+          else if arg == "colors" then
+            config.lib.stylix.${arg}
+          else
+            config.stylix.${arg}
+              or (throw "stylix: Stylix arg was requested by mk-target but does not exist: ${arg}")
+        );
+      mkConfig = fn: fn (getStylixAttrs fn);
+      mkConditionalConfig =
+        fn:
+        let
+          attrs = functionArgNames fn;
path: stylix/mk-target.nix
user: login: MattSturgeon
body: Kinda confusing, but attrs here is a _list_ of attr _names_. IMO the name strongly implies an attrset.

Maybe `names` would be slightly better than `attrs`, however that's also pretty vague.

Should we be referring to these as _stylix option_ names rather than args or attrs? Does that better fit the intent?
created_at: 2025-05-05T01:40:36Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,77 @@
+{
+  # name used for creation of options
+  name,
+
+  # name used for description of options
+  humanName,
+
+  # condition for automatically enabling this target
+  autoEnable ? true,
+
+  # options to be added under `options.stylix.targets.${name}`
+  extraOptions ? { },
+
+  # list of functions with args representing Stylix options; function results
+  # only merged if all relevant Stylix options are not null
+  configElements ? [ ],
+
+  # function with args representing Stylix options; function result always
+  # merged
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args, while using this as their module body
+let
+  configModule =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+      getStylixAttrs =
+        fn:
+        lib.genAttrs (functionArgNames fn) (
+          arg:
+          if arg == "cfg" then
+            cfg
+          else if arg == "colors" then
+            config.lib.stylix.${arg}
+          else
+            config.stylix.${arg}
+              or (throw "stylix: Stylix arg was requested by mk-target but does not exist: ${arg}")
+        );
+      mkConfig = fn: fn (getStylixAttrs fn);
+      mkConditionalConfig =
+        fn:
+        let
+          attrs = functionArgNames fn;
+        in
+        lib.mkIf (builtins.all (attr: attr != null) attrs) (fn (getStylixAttrs fn));
path: stylix/mk-target.nix
user: login: MattSturgeon
body: I don't understand in what scenario one of the attrNames could be `null`.

These names come from `functionArgNames`, which returns the `attrNames` list of the function args set.

`builtins.attrNames` cannot return a null list item, elements in the list will all be strings.

Some comments and/or more descriptive names may help understand the intent, but I suspect something is broken here.
created_at: 2025-05-05T01:44:34Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,77 @@
+{
+  # name used for creation of options
+  name,
+
+  # name used for description of options
+  humanName,
+
+  # condition for automatically enabling this target
+  autoEnable ? true,
+
+  # options to be added under `options.stylix.targets.${name}`
+  extraOptions ? { },
+
+  # list of functions with args representing Stylix options; function results
+  # only merged if all relevant Stylix options are not null
+  configElements ? [ ],
+
+  # function with args representing Stylix options; function result always
+  # merged
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args, while using this as their module body
+let
+  configModule =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+      getStylixAttrs =
+        fn:
+        lib.genAttrs (functionArgNames fn) (
+          arg:
+          if arg == "cfg" then
+            cfg
+          else if arg == "colors" then
+            config.lib.stylix.${arg}
path: stylix/mk-target.nix
user: login: MattSturgeon
body: As-per the if condition, this doesn't need to be dynamic:

```suggestion
            config.lib.stylix.colors
```

created_at: 2025-05-05T01:50:26Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,77 @@
+{
+  # name used for creation of options
+  name,
+
+  # name used for description of options
+  humanName,
+
+  # condition for automatically enabling this target
+  autoEnable ? true,
+
+  # options to be added under `options.stylix.targets.${name}`
+  extraOptions ? { },
+
+  # list of functions with args representing Stylix options; function results
+  # only merged if all relevant Stylix options are not null
+  configElements ? [ ],
+
+  # function with args representing Stylix options; function result always
+  # merged
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args, while using this as their module body
+let
+  configModule =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+      getStylixAttrs =
+        fn:
+        lib.genAttrs (functionArgNames fn) (
+          arg:
+          if arg == "cfg" then
+            cfg
+          else if arg == "colors" then
+            config.lib.stylix.${arg}
+          else
+            config.stylix.${arg}
+              or (throw "stylix: Stylix arg was requested by mk-target but does not exist: ${arg}")
+        );
+      mkConfig = fn: fn (getStylixAttrs fn);
+      mkConditionalConfig =
+        fn:
+        let
+          attrs = functionArgNames fn;
+        in
+        lib.mkIf (builtins.all (attr: attr != null) attrs) (fn (getStylixAttrs fn));
path: stylix/mk-target.nix
user: login: MattSturgeon
body: Perhaps the intent was to check the actual option values, rather than the option names?
created_at: 2025-05-05T01:55:32Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,77 @@
+{
+  # name used for creation of options
+  name,
+
+  # name used for description of options
+  humanName,
+
+  # condition for automatically enabling this target
+  autoEnable ? true,
+
+  # options to be added under `options.stylix.targets.${name}`
+  extraOptions ? { },
+
+  # list of functions with args representing Stylix options; function results
+  # only merged if all relevant Stylix options are not null
+  configElements ? [ ],
+
+  # function with args representing Stylix options; function result always
+  # merged
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args, while using this as their module body
+let
+  configModule =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+      getStylixAttrs =
+        fn:
+        lib.genAttrs (functionArgNames fn) (
+          arg:
+          if arg == "cfg" then
+            cfg
+          else if arg == "colors" then
+            config.lib.stylix.${arg}
+          else
+            config.stylix.${arg}
+              or (throw "stylix: Stylix arg was requested by mk-target but does not exist: ${arg}")
+        );
+      mkConfig = fn: fn (getStylixAttrs fn);
+      mkConditionalConfig =
+        fn:
+        let
+          attrs = functionArgNames fn;
path: stylix/mk-target.nix
user: login: Flameopathic
body: This was, in fact, supposed to be the attributes themselves. I think that more consistent language would be helpful, but I worry about referencing the set Stylix configuration attributes as options due to possible ambiguity between the `config` values and the `options` values. If you think this would not be a problem, I could change the vocabulary around it, and I'm not sure of what better to call them.
created_at: 2025-05-05T18:35:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -34,44 +34,47 @@ Other files needed by the module can also be stored within the
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`.
-This is similar to
-[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
-from the standard library, however it integrates with
-[`stylix.enable`](./options/nixos.md#stylixenable) and
-[`stylix.autoEnable`](./options/nixos.md#stylixautoenable)
-and generates more specific documentation.
+All modules should be created using `mkTarget`.
 
 A general format for modules is shown below.
 
 ```nix
+mkTarget:
 { config, lib, ... }:
+mkTarget
 {
-  options.stylix.targets.«name».enable =
-    config.lib.stylix.mkEnableTarget "«human readable name»" true;
+  name = "«target attribute name»";
+  humanName = "«human readable name»";
 
-  config =
-    lib.mkIf (config.stylix.enable && config.stylix.targets.«name».enable)
+  configElements = [
+    { colors }:
       {
         programs.«name».backgroundColor = config.lib.stylix.colors.base00;
       };
+  ];
 }
 ```
 
-The human readable name will be inserted into the following sentence:
+### `mkTarget`
 
-> Whether to enable theming for «human readable name».
+The `mkTarget` function is a special function which automatically generates
+options and guards configuration with`stylix.${attr} != null` on configuration
+to cut down on boilerplate.
 
-If your module will touch options outside of `programs.«name»` or `services.«name»`,
-it should include an additional condition in `mkIf` to prevent any effects
-when the target is not installed.
+Arguments without defaults must be set explicitly.
 
-The boolean value after `mkEnableTarget` should be changed to `false` if
-one of the following applies:
+| Argument | Type | Default | Description | Example |
+|----------|------|---------|-------------|---------|
+|`name`|String||Used when generating options; `stylix.targets.${name}`|"firefox"|
+|`humanName`|String||Used when generating enable option; `stylix.targets.${name}.enable` has a description of "Whether to enable theming for ${humanName}."|"Firefox"|
+|`autoEnableConditions`|Boolean|true|Used for determining when an attribute called `autoEnable` should be set to `false` if one of the following applies:<ul><li>The module requires further manual setup to work correctly.</li><li>There is no reliable way to detect whether the target is installed, *and* enabling it unconditionally would cause problems.</li></ul>|`pkgs.stdenv.hostPlatform.isLinux`|
+|`extraOptions`|Attrset of options|{ }|Attributes will be mapped to `stylix.targets.${name}.${option}`|`{ colorTheme.enable = lib.mkEnableOption "color theme"; }`|
+|`configElements`|List of function returning attrset|{ }|Each function should take the Stylix attributes that it needs (e.g. `colors`, `image`, `fonts`) and `cfg` if necessary. This `cfg` is an alias for `config.stylix.targets.${name}`. Each function should return an attribute set to be merged into `config`. If any of the Stylix attributes needed by a function are `null`, the configuration will not be merged.|*See above template*|
+|`generalConfig`|Null or function returning attrset|null|Same function format as in `configElements`, except the result of this function will always be merged with `config`. Avoid use when possible.|*See above template*|
path: docs/src/modules.md
user: login: Flameopathic
body: Wording was unclear, `generalConfig` is only different from `configElements` in that it does not have its attribute requests checked if they are null. Because of that, there is no reason for it to be able to take attrs as it would be duplicated behavior from `configElements`
created_at: 2025-05-05T18:50:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,77 @@
+{
+  # name used for creation of options
+  name,
+
+  # name used for description of options
+  humanName,
+
+  # condition for automatically enabling this target
+  autoEnable ? true,
+
+  # options to be added under `options.stylix.targets.${name}`
+  extraOptions ? { },
+
+  # list of functions with args representing Stylix options; function results
+  # only merged if all relevant Stylix options are not null
+  configElements ? [ ],
+
+  # function with args representing Stylix options; function result always
+  # merged
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args, while using this as their module body
+let
+  configModule =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+      getStylixAttrs =
+        fn:
+        lib.genAttrs (functionArgNames fn) (
+          arg:
+          if arg == "cfg" then
+            cfg
+          else if arg == "colors" then
+            config.lib.stylix.${arg}
+          else
+            config.stylix.${arg}
+              or (throw "stylix: Stylix arg was requested by mk-target but does not exist: ${arg}")
+        );
+      mkConfig = fn: fn (getStylixAttrs fn);
+      mkConditionalConfig =
+        fn:
+        let
+          attrs = functionArgNames fn;
path: stylix/mk-target.nix
user: login: MattSturgeon
body: 100%, there's nothing harder than naming!

`config` and `options` both contain options (kida); `options` has the "full" options while `config` maps all of them to their `<opt>.value` value.

I agree calling them “options” could also be confusing though. The current name is fine, and makes sense now that I know is was supposed to represent attrs not attr-names.
created_at: 2025-05-05T19:06:54Z
author_association: MEMBER
diff_hunk: @@ -34,44 +34,47 @@ Other files needed by the module can also be stored within the
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`.
-This is similar to
-[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
-from the standard library, however it integrates with
-[`stylix.enable`](./options/nixos.md#stylixenable) and
-[`stylix.autoEnable`](./options/nixos.md#stylixautoenable)
-and generates more specific documentation.
+All modules should be created using `mkTarget`.
 
 A general format for modules is shown below.
 
 ```nix
+mkTarget:
 { config, lib, ... }:
+mkTarget
 {
-  options.stylix.targets.«name».enable =
-    config.lib.stylix.mkEnableTarget "«human readable name»" true;
+  name = "«target attribute name»";
+  humanName = "«human readable name»";
 
-  config =
-    lib.mkIf (config.stylix.enable && config.stylix.targets.«name».enable)
+  configElements = [
+    { colors }:
       {
         programs.«name».backgroundColor = config.lib.stylix.colors.base00;
       };
+  ];
 }
 ```
 
-The human readable name will be inserted into the following sentence:
+### `mkTarget`
 
-> Whether to enable theming for «human readable name».
+The `mkTarget` function is a special function which automatically generates
+options and guards configuration with`stylix.${attr} != null` on configuration
+to cut down on boilerplate.
 
-If your module will touch options outside of `programs.«name»` or `services.«name»`,
-it should include an additional condition in `mkIf` to prevent any effects
-when the target is not installed.
+Arguments without defaults must be set explicitly.
 
-The boolean value after `mkEnableTarget` should be changed to `false` if
-one of the following applies:
+| Argument | Type | Default | Description | Example |
+|----------|------|---------|-------------|---------|
+|`name`|String||Used when generating options; `stylix.targets.${name}`|"firefox"|
+|`humanName`|String||Used when generating enable option; `stylix.targets.${name}.enable` has a description of "Whether to enable theming for ${humanName}."|"Firefox"|
+|`autoEnable`|Boolean|true|Whether the target should be auto-enabled. Should be set to `false` if one of the following applies:<ul><li>The module requires further manual setup to work correctly.</li><li>There is no reliable way to detect whether the target is installed, *and* enabling it unconditionally would cause problems.</li></ul>|`false`|
+|`extraOptions`|Attrset of options|{ }|Attributes will be mapped to `stylix.targets.${name}.${option}`|`{ colorTheme.enable = lib.mkEnableOption "color theme"; }`|
+|`configElements`|List of or single (attrset or function returning attrset)|{ }|Function(s) should take the Stylix attributes that it needs (e.g. `colors`, `image`, `fonts`) and `cfg` if necessary. This `cfg` is an alias for `config.stylix.targets.${name}`. Function(s) should return an attribute set to be merged into `config`. If any of the Stylix attributes needed by a/the function are `null`, the configuration will not be merged.|*See above template*|
+|`generalConfig`|Null or function returning attrset|null|Same format as functions under `configElements`. Merged regardless of whether requested Stylix options are `null`. Avoid use when possible.|*See above template*|
path: docs/src/modules.md
user: login: MattSturgeon
body: Q: in what scenario(s) would requested attrs be null? As an outsider this isn't clear to me.

Is this related to optional third-party modules, e.g., nixvim's home-manager module?

Not easy to come up with a concise description, but maybe the intention could be made clearer, or a link to more in-depth docs could be added?
created_at: 2025-05-05T22:12:34Z
author_association: MEMBER
diff_hunk: @@ -34,44 +34,47 @@ Other files needed by the module can also be stored within the
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`.
-This is similar to
-[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
-from the standard library, however it integrates with
-[`stylix.enable`](./options/nixos.md#stylixenable) and
-[`stylix.autoEnable`](./options/nixos.md#stylixautoenable)
-and generates more specific documentation.
+All modules should be created using `mkTarget`.
 
 A general format for modules is shown below.
 
 ```nix
+mkTarget:
 { config, lib, ... }:
+mkTarget
 {
-  options.stylix.targets.«name».enable =
-    config.lib.stylix.mkEnableTarget "«human readable name»" true;
+  name = "«target attribute name»";
+  humanName = "«human readable name»";
 
-  config =
-    lib.mkIf (config.stylix.enable && config.stylix.targets.«name».enable)
+  configElements = [
+    { colors }:
       {
         programs.«name».backgroundColor = config.lib.stylix.colors.base00;
       };
+  ];
 }
 ```
 
-The human readable name will be inserted into the following sentence:
+### `mkTarget`
 
-> Whether to enable theming for «human readable name».
+The `mkTarget` function is a special function which automatically generates
+options and guards configuration with`stylix.${attr} != null` on configuration
+to cut down on boilerplate.
 
-If your module will touch options outside of `programs.«name»` or `services.«name»`,
-it should include an additional condition in `mkIf` to prevent any effects
-when the target is not installed.
+Arguments without defaults must be set explicitly.
 
-The boolean value after `mkEnableTarget` should be changed to `false` if
-one of the following applies:
+| Argument | Type | Default | Description | Example |
+|----------|------|---------|-------------|---------|
+|`name`|String||Used when generating options; `stylix.targets.${name}`|"firefox"|
+|`humanName`|String||Used when generating enable option; `stylix.targets.${name}.enable` has a description of "Whether to enable theming for ${humanName}."|"Firefox"|
+|`autoEnable`|Boolean|true|Whether the target should be auto-enabled. Should be set to `false` if one of the following applies:<ul><li>The module requires further manual setup to work correctly.</li><li>There is no reliable way to detect whether the target is installed, *and* enabling it unconditionally would cause problems.</li></ul>|`false`|
+|`extraOptions`|Attrset of options|{ }|Attributes will be mapped to `stylix.targets.${name}.${option}`|`{ colorTheme.enable = lib.mkEnableOption "color theme"; }`|
path: docs/src/modules.md
user: login: MattSturgeon
body: ```suggestion
|`extraOptions`|Attrset of option declarations|{ }|Attributes will be mapped to `stylix.targets.${name}.${option}`|`{ colorTheme.enable = lib.mkEnableOption "color theme"; }`|
```

When mentioning options its usually a good idea to clarify whether we mean declarations (`options.foo = mkOption {}`) or definitions (`config.foo = "bar"`).

Unrelated: is it important to name the argument `extraOptions` instead of simply `options`? I guess it makes clear this is a custom abstraction. Maybe `targetOptions` is a better name, although that should be clear from the fact we're calling `mkTarget` :thinking: ?
created_at: 2025-05-05T22:15:58Z
author_association: MEMBER
diff_hunk: @@ -34,44 +34,47 @@ Other files needed by the module can also be stored within the
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`.
-This is similar to
-[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
-from the standard library, however it integrates with
-[`stylix.enable`](./options/nixos.md#stylixenable) and
-[`stylix.autoEnable`](./options/nixos.md#stylixautoenable)
-and generates more specific documentation.
+All modules should be created using `mkTarget`.
 
 A general format for modules is shown below.
 
 ```nix
+mkTarget:
 { config, lib, ... }:
+mkTarget
 {
-  options.stylix.targets.«name».enable =
-    config.lib.stylix.mkEnableTarget "«human readable name»" true;
+  name = "«target attribute name»";
+  humanName = "«human readable name»";
 
-  config =
-    lib.mkIf (config.stylix.enable && config.stylix.targets.«name».enable)
+  configElements = [
+    { colors }:
       {
         programs.«name».backgroundColor = config.lib.stylix.colors.base00;
       };
+  ];
path: docs/src/modules.md
user: login: MattSturgeon
body: This has a syntax error, list element should be wrapped with `(`parens`)` because it contains whitespace.

Alternatively, you could use the new non-list style as it is a single element:

```suggestion
  configElements =
    { colors }:
    {
      programs.«name».backgroundColor = colors.base00;
    };
```

Also: https://github.com/danth/stylix/pull/1130#discussion_r2074415048
created_at: 2025-05-05T22:18:45Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,80 @@
+{
+  # name used for creation of options
+  name,
+
+  # name used for description of options
+  humanName,
+
+  # condition for automatically enabling this target
+  autoEnable ? true,
+
+  # options to be added under `options.stylix.targets.${name}`
+  extraOptions ? { },
+
+  # list of functions with args representing Stylix options; function results
+  # only merged if all relevant Stylix options are not null
path: stylix/mk-target.nix
user: login: MattSturgeon
body: Should roughly be in sync with the desc in docs/src/modules.md 
created_at: 2025-05-05T22:21:27Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,80 @@
+{
+  # name used for creation of options
+  name,
+
+  # name used for description of options
+  humanName,
+
+  # condition for automatically enabling this target
+  autoEnable ? true,
+
+  # options to be added under `options.stylix.targets.${name}`
+  extraOptions ? { },
+
+  # list of functions with args representing Stylix options; function results
+  # only merged if all relevant Stylix options are not null
+  configElements ? [ ],
+
+  # function with args representing Stylix options; function result always
+  # merged
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args, while using this as their module body
+let
+  configModule =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+      getStylixAttrs =
+        fn:
+        lib.genAttrs (functionArgNames fn) (
+          arg:
+          if arg == "cfg" then
+            cfg
+          else if arg == "colors" then
+            config.lib.stylix.colors
+          else
+            config.stylix.${arg}
+              or (throw "stylix: Stylix arg was requested by mk-target but does not exist: ${arg}")
+        );
+      mkConfig = fn: fn (getStylixAttrs fn);
+      mkConditionalConfig =
+        c:
+        if builtins.isFunction c then
+          let
+            attrs = builtins.attrValues (getStylixAttrs c);
+          in
+          lib.mkIf (builtins.all (attr: attr != null) attrs) (mkConfig c)
path: stylix/mk-target.nix
user: login: MattSturgeon
body: May read better with a named condition?

```suggestion
          let
            attrs = builtins.attrValues (getStylixAttrs c);
            allAttrsNonNull = builtins.all (attr: attr != null) attrs;
          in
          lib.mkIf allAttrsNonNull (mkConfig c)
```

Alt names might be `allAttrsDefined` or `allAttrsExist` or `allAttrsPresent`.

I could nit-pick that being "defined" has its own meaning in the option system. Or that an attr's presence/existence usually means `attrs ? foo` not `attrs.foo != null`. But despite that, those names get the intention across.
created_at: 2025-05-05T22:25:50Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,80 @@
+{
+  # name used for creation of options
+  name,
+
+  # name used for description of options
+  humanName,
+
+  # condition for automatically enabling this target
+  autoEnable ? true,
+
+  # options to be added under `options.stylix.targets.${name}`
+  extraOptions ? { },
+
+  # list of functions with args representing Stylix options; function results
+  # only merged if all relevant Stylix options are not null
+  configElements ? [ ],
+
+  # function with args representing Stylix options; function result always
+  # merged
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args, while using this as their module body
+let
+  configModule =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+      functionArgNames =
path: stylix/mk-target.nix
user: login: MattSturgeon
body: ```suggestion

      # Get the function's de-structured arg names as a list
      functionArgNames =
```
created_at: 2025-05-05T22:27:27Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,80 @@
+{
+  # name used for creation of options
+  name,
+
+  # name used for description of options
+  humanName,
+
+  # condition for automatically enabling this target
+  autoEnable ? true,
+
+  # options to be added under `options.stylix.targets.${name}`
+  extraOptions ? { },
+
+  # list of functions with args representing Stylix options; function results
+  # only merged if all relevant Stylix options are not null
+  configElements ? [ ],
+
+  # function with args representing Stylix options; function result always
+  # merged
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args, while using this as their module body
+let
+  configModule =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+      getStylixAttrs =
path: stylix/mk-target.nix
user: login: MattSturgeon
body: You could definitely improve on my wording, but it may be nice to describe the function:

```suggestion

      # Get the stylix attrs (option values) requested by the function's de-structured args
      getStylixAttrs =
```
created_at: 2025-05-05T22:28:57Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,80 @@
+{
+  # name used for creation of options
+  name,
+
+  # name used for description of options
+  humanName,
+
+  # condition for automatically enabling this target
+  autoEnable ? true,
+
+  # options to be added under `options.stylix.targets.${name}`
+  extraOptions ? { },
+
+  # list of functions with args representing Stylix options; function results
+  # only merged if all relevant Stylix options are not null
+  configElements ? [ ],
+
+  # function with args representing Stylix options; function result always
+  # merged
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args, while using this as their module body
+let
+  configModule =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+      getStylixAttrs =
+        fn:
+        lib.genAttrs (functionArgNames fn) (
+          arg:
+          if arg == "cfg" then
+            cfg
+          else if arg == "colors" then
+            config.lib.stylix.colors
+          else
+            config.stylix.${arg}
+              or (throw "stylix: Stylix arg was requested by mk-target but does not exist: ${arg}")
+        );
+      mkConfig = fn: fn (getStylixAttrs fn);
path: stylix/mk-target.nix
user: login: MattSturgeon
body: ```suggestion

      # Call the config element function with the requested stylix attr values
      # Returns a config definition
      mkConfig = fn: fn (getStylixAttrs fn);
```
created_at: 2025-05-05T22:30:01Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,80 @@
+{
+  # name used for creation of options
+  name,
+
+  # name used for description of options
+  humanName,
+
+  # condition for automatically enabling this target
+  autoEnable ? true,
+
+  # options to be added under `options.stylix.targets.${name}`
+  extraOptions ? { },
+
+  # list of functions with args representing Stylix options; function results
+  # only merged if all relevant Stylix options are not null
+  configElements ? [ ],
+
+  # function with args representing Stylix options; function result always
+  # merged
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args, while using this as their module body
+let
+  configModule =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+      getStylixAttrs =
+        fn:
+        lib.genAttrs (functionArgNames fn) (
+          arg:
+          if arg == "cfg" then
+            cfg
+          else if arg == "colors" then
+            config.lib.stylix.colors
+          else
+            config.stylix.${arg}
+              or (throw "stylix: Stylix arg was requested by mk-target but does not exist: ${arg}")
+        );
+      mkConfig = fn: fn (getStylixAttrs fn);
+      mkConditionalConfig =
path: stylix/mk-target.nix
user: login: MattSturgeon
body: ```suggestion

      # Wrap the config retuned by the config element with the appropriate `mkIf` condition
      # If the config element is a function, call it using `mkConfig` (above)
      # Returns a config definition wrapped with `mkIf`
      mkConditionalConfig =
```
created_at: 2025-05-05T22:32:02Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,80 @@
+{
+  # name used for creation of options
+  name,
+
+  # name used for description of options
+  humanName,
+
+  # condition for automatically enabling this target
+  autoEnable ? true,
+
+  # options to be added under `options.stylix.targets.${name}`
+  extraOptions ? { },
+
+  # list of functions with args representing Stylix options; function results
+  # only merged if all relevant Stylix options are not null
+  configElements ? [ ],
+
+  # function with args representing Stylix options; function result always
+  # merged
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args, while using this as their module body
+let
+  configModule =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+      getStylixAttrs =
+        fn:
+        lib.genAttrs (functionArgNames fn) (
+          arg:
+          if arg == "cfg" then
+            cfg
+          else if arg == "colors" then
+            config.lib.stylix.colors
+          else
+            config.stylix.${arg}
+              or (throw "stylix: Stylix arg was requested by mk-target but does not exist: ${arg}")
+        );
+      mkConfig = fn: fn (getStylixAttrs fn);
+      mkConditionalConfig =
+        c:
+        if builtins.isFunction c then
+          let
+            attrs = builtins.attrValues (getStylixAttrs c);
+          in
+          lib.mkIf (builtins.all (attr: attr != null) attrs) (mkConfig c)
+        else
+          c;
+    in
+    {
+      config = lib.mkIf (config.stylix.enable && cfg.enable) (
+        lib.mkMerge (
+          lib.optional (generalConfig != null) (mkConfig generalConfig)
+          ++ map mkConditionalConfig (lib.toList configElements)
+        )
+      );
+    };
+in
+{
+  imports = [
+    (
+      { config, ... }:
+      {
+        options.stylix.targets.${name}.enable =
+          config.lib.stylix.mkEnableTarget humanName autoEnable;
path: stylix/mk-target.nix
user: login: MattSturgeon
body: Very minor & subjective: would this be easier to read and less verbose if the `enable` option declaration was moved up to L57?

If so, `configModule` may need renaming to e.g. `module`?

```nix
# Line 57
{
  options.stylix.targets.${name}.enable =
    config.lib.stylix.mkEnableTarget humanName autoEnable;

  config = lib.mkIf (config.stylix.enable && cfg.enable) (
    lib.mkMerge (
      lib.optional (generalConfig != null) (mkConfig generalConfig)
      ++ map mkConditionalConfig (lib.toList configElements)
    )
  );
};
```

You're welcome to disagree :sweat_smile: 
created_at: 2025-05-05T22:34:19Z
author_association: MEMBER
diff_hunk: @@ -34,44 +34,47 @@ Other files needed by the module can also be stored within the
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`.
-This is similar to
-[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
-from the standard library, however it integrates with
-[`stylix.enable`](./options/nixos.md#stylixenable) and
-[`stylix.autoEnable`](./options/nixos.md#stylixautoenable)
-and generates more specific documentation.
+All modules should be created using `mkTarget`.
path: docs/src/modules.md
user: login: MattSturgeon
body: This is very firm. Is that intended?

Is there still a place for modules that manually use `mkEnableTarget` themselves?
created_at: 2025-05-05T22:36:32Z
author_association: MEMBER
diff_hunk: @@ -34,44 +34,47 @@ Other files needed by the module can also be stored within the
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`.
-This is similar to
-[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
-from the standard library, however it integrates with
-[`stylix.enable`](./options/nixos.md#stylixenable) and
-[`stylix.autoEnable`](./options/nixos.md#stylixautoenable)
-and generates more specific documentation.
+All modules should be created using `mkTarget`.
 
 A general format for modules is shown below.
path: docs/src/modules.md
user: login: MattSturgeon
body: These docs should have some passing mention of the fact that `mkTarget` is passed _before_ the optional module args. And that it is only done if the module's first arg is a non-destructured arg.

Shouldn't go too in-depth with the technical reasoning, but it's worth highlighting the unusual pattern.
created_at: 2025-05-05T22:38:20Z
author_association: MEMBER
diff_hunk: @@ -8,7 +8,19 @@ builtins.concatLists (
     path: kind:
     let
       file = "${inputs.self}/modules/${path}/${for}.nix";
+      module = import file;
+      # Assume that a function-module whose first arg doesn't have any de-
+      # structured attrs is actually expecting `mkTarget` as its first arg;
+      # `mkTarget` cannot be distribtued normally through the module system
path: stylix/autoload.nix
user: login: MattSturgeon
body: _Super_ minor: I think this creates a clearer separation of thoughts between the two comments. Makes it easier to notice the second point when skim-reading.

```suggestion
      #
      # `mkTarget` cannot be distribtued normally through the module system
```

?
created_at: 2025-05-05T22:39:32Z
author_association: MEMBER
diff_hunk: @@ -34,44 +34,47 @@ Other files needed by the module can also be stored within the
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`.
-This is similar to
-[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
-from the standard library, however it integrates with
-[`stylix.enable`](./options/nixos.md#stylixenable) and
-[`stylix.autoEnable`](./options/nixos.md#stylixautoenable)
-and generates more specific documentation.
+All modules should be created using `mkTarget`.
path: docs/src/modules.md
user: login: 0xda157
body: mkTarget doesn't cover some edge cases e.g. the firefox module. We might want to leave a note about that.
created_at: 2025-05-05T23:54:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -34,44 +34,47 @@ Other files needed by the module can also be stored within the
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`.
-This is similar to
-[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
-from the standard library, however it integrates with
-[`stylix.enable`](./options/nixos.md#stylixenable) and
-[`stylix.autoEnable`](./options/nixos.md#stylixautoenable)
-and generates more specific documentation.
+All modules should be created using `mkTarget`.
 
 A general format for modules is shown below.
 
 ```nix
+mkTarget:
 { config, lib, ... }:
+mkTarget
 {
-  options.stylix.targets.«name».enable =
-    config.lib.stylix.mkEnableTarget "«human readable name»" true;
+  name = "«target attribute name»";
+  humanName = "«human readable name»";
 
-  config =
-    lib.mkIf (config.stylix.enable && config.stylix.targets.«name».enable)
+  configElements = [
+    { colors }:
       {
         programs.«name».backgroundColor = config.lib.stylix.colors.base00;
path: docs/src/modules.md
user: login: 0xda157
body: ```suggestion
        programs.«name».backgroundColor = colors.base00;
```
created_at: 2025-05-05T23:55:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -34,44 +34,47 @@ Other files needed by the module can also be stored within the
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`.
-This is similar to
-[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
-from the standard library, however it integrates with
-[`stylix.enable`](./options/nixos.md#stylixenable) and
-[`stylix.autoEnable`](./options/nixos.md#stylixautoenable)
-and generates more specific documentation.
+All modules should be created using `mkTarget`.
 
 A general format for modules is shown below.
 
 ```nix
+mkTarget:
 { config, lib, ... }:
path: docs/src/modules.md
user: login: 0xda157
body: ```suggestion
```
created_at: 2025-05-05T23:55:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -34,44 +34,47 @@ Other files needed by the module can also be stored within the
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`.
-This is similar to
-[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
-from the standard library, however it integrates with
-[`stylix.enable`](./options/nixos.md#stylixenable) and
-[`stylix.autoEnable`](./options/nixos.md#stylixautoenable)
-and generates more specific documentation.
+All modules should be created using `mkTarget`.
 
 A general format for modules is shown below.
 
 ```nix
+mkTarget:
 { config, lib, ... }:
+mkTarget
 {
-  options.stylix.targets.«name».enable =
-    config.lib.stylix.mkEnableTarget "«human readable name»" true;
+  name = "«target attribute name»";
+  humanName = "«human readable name»";
 
-  config =
-    lib.mkIf (config.stylix.enable && config.stylix.targets.«name».enable)
+  configElements = [
+    { colors }:
       {
         programs.«name».backgroundColor = config.lib.stylix.colors.base00;
       };
+  ];
 }
 ```
 
-The human readable name will be inserted into the following sentence:
+### `mkTarget`
 
-> Whether to enable theming for «human readable name».
+The `mkTarget` function is a special function which automatically generates
+options and guards configuration with`stylix.${attr} != null` on configuration
+to cut down on boilerplate.
 
-If your module will touch options outside of `programs.«name»` or `services.«name»`,
-it should include an additional condition in `mkIf` to prevent any effects
-when the target is not installed.
+Arguments without defaults must be set explicitly.
 
-The boolean value after `mkEnableTarget` should be changed to `false` if
-one of the following applies:
+| Argument | Type | Default | Description | Example |
path: docs/src/modules.md
user: login: 0xda157
body: please format this table to make it more readable when viewed as plain text
created_at: 2025-05-05T23:56:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -34,44 +34,47 @@ Other files needed by the module can also be stored within the
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`.
-This is similar to
-[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
-from the standard library, however it integrates with
-[`stylix.enable`](./options/nixos.md#stylixenable) and
-[`stylix.autoEnable`](./options/nixos.md#stylixautoenable)
-and generates more specific documentation.
+All modules should be created using `mkTarget`.
 
 A general format for modules is shown below.
 
 ```nix
+mkTarget:
 { config, lib, ... }:
+mkTarget
 {
-  options.stylix.targets.«name».enable =
-    config.lib.stylix.mkEnableTarget "«human readable name»" true;
+  name = "«target attribute name»";
+  humanName = "«human readable name»";
 
-  config =
-    lib.mkIf (config.stylix.enable && config.stylix.targets.«name».enable)
+  configElements = [
+    { colors }:
       {
         programs.«name».backgroundColor = config.lib.stylix.colors.base00;
       };
+  ];
 }
 ```
 
-The human readable name will be inserted into the following sentence:
+### `mkTarget`
 
-> Whether to enable theming for «human readable name».
+The `mkTarget` function is a special function which automatically generates
+options and guards configuration with`stylix.${attr} != null` on configuration
+to cut down on boilerplate.
 
-If your module will touch options outside of `programs.«name»` or `services.«name»`,
-it should include an additional condition in `mkIf` to prevent any effects
-when the target is not installed.
+Arguments without defaults must be set explicitly.
 
-The boolean value after `mkEnableTarget` should be changed to `false` if
-one of the following applies:
+| Argument | Type | Default | Description | Example |
+|----------|------|---------|-------------|---------|
+|`name`|String||Used when generating options; `stylix.targets.${name}`|"firefox"|
+|`humanName`|String||Used when generating enable option; `stylix.targets.${name}.enable` has a description of "Whether to enable theming for ${humanName}."|"Firefox"|
+|`autoEnable`|Boolean|true|Whether the target should be auto-enabled. Should be set to `false` if one of the following applies:<ul><li>The module requires further manual setup to work correctly.</li><li>There is no reliable way to detect whether the target is installed, *and* enabling it unconditionally would cause problems.</li></ul>|`false`|
+|`extraOptions`|Attrset of options|{ }|Attributes will be mapped to `stylix.targets.${name}.${option}`|`{ colorTheme.enable = lib.mkEnableOption "color theme"; }`|
+|`configElements`|List of or single (attrset or function returning attrset)|{ }|Function(s) should take the Stylix attributes that it needs (e.g. `colors`, `image`, `fonts`) and `cfg` if necessary. This `cfg` is an alias for `config.stylix.targets.${name}`. Function(s) should return an attribute set to be merged into `config`. If any of the Stylix attributes needed by a/the function are `null`, the configuration will not be merged.|*See above template*|
path: docs/src/modules.md
user: login: 0xda157
body: should also note somewhere that `config.stylix.*`, `config.lib.stylix.colors.*`, etc should never be accessed inside of configElements through `config`
created_at: 2025-05-05T23:59:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -34,44 +34,47 @@ Other files needed by the module can also be stored within the
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`.
-This is similar to
-[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
-from the standard library, however it integrates with
-[`stylix.enable`](./options/nixos.md#stylixenable) and
-[`stylix.autoEnable`](./options/nixos.md#stylixautoenable)
-and generates more specific documentation.
+All modules should be created using `mkTarget`.
 
 A general format for modules is shown below.
 
 ```nix
+mkTarget:
 { config, lib, ... }:
+mkTarget
 {
-  options.stylix.targets.«name».enable =
-    config.lib.stylix.mkEnableTarget "«human readable name»" true;
+  name = "«target attribute name»";
+  humanName = "«human readable name»";
 
-  config =
-    lib.mkIf (config.stylix.enable && config.stylix.targets.«name».enable)
+  configElements = [
+    { colors }:
       {
         programs.«name».backgroundColor = config.lib.stylix.colors.base00;
       };
+  ];
 }
 ```
 
-The human readable name will be inserted into the following sentence:
+### `mkTarget`
 
-> Whether to enable theming for «human readable name».
+The `mkTarget` function is a special function which automatically generates
+options and guards configuration with`stylix.${attr} != null` on configuration
+to cut down on boilerplate.
 
-If your module will touch options outside of `programs.«name»` or `services.«name»`,
-it should include an additional condition in `mkIf` to prevent any effects
-when the target is not installed.
+Arguments without defaults must be set explicitly.
 
-The boolean value after `mkEnableTarget` should be changed to `false` if
-one of the following applies:
+| Argument | Type | Default | Description | Example |
path: docs/src/modules.md
user: login: MattSturgeon
body: pros & cons to having the column dividers aligned:
- future changes that edit one line may need to reformat the entire table
- the table may end up _very_ wide
- the table is easier to read
- easier to tell which column a specific field is in

Even without strictly aligning the columns, it should be possible to format slightly more readably.
created_at: 2025-05-06T00:14:48Z
author_association: MEMBER
diff_hunk: @@ -34,44 +34,47 @@ Other files needed by the module can also be stored within the
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`.
-This is similar to
-[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
-from the standard library, however it integrates with
-[`stylix.enable`](./options/nixos.md#stylixenable) and
-[`stylix.autoEnable`](./options/nixos.md#stylixautoenable)
-and generates more specific documentation.
+All modules should be created using `mkTarget`.
 
 A general format for modules is shown below.
 
 ```nix
+mkTarget:
 { config, lib, ... }:
+mkTarget
 {
-  options.stylix.targets.«name».enable =
-    config.lib.stylix.mkEnableTarget "«human readable name»" true;
+  name = "«target attribute name»";
+  humanName = "«human readable name»";
 
-  config =
-    lib.mkIf (config.stylix.enable && config.stylix.targets.«name».enable)
+  configElements = [
+    { colors }:
       {
         programs.«name».backgroundColor = config.lib.stylix.colors.base00;
       };
+  ];
 }
 ```
 
-The human readable name will be inserted into the following sentence:
+### `mkTarget`
 
-> Whether to enable theming for «human readable name».
+The `mkTarget` function is a special function which automatically generates
+options and guards configuration with`stylix.${attr} != null` on configuration
+to cut down on boilerplate.
 
-If your module will touch options outside of `programs.«name»` or `services.«name»`,
-it should include an additional condition in `mkIf` to prevent any effects
-when the target is not installed.
+Arguments without defaults must be set explicitly.
 
-The boolean value after `mkEnableTarget` should be changed to `false` if
-one of the following applies:
+| Argument | Type | Default | Description | Example |
+|----------|------|---------|-------------|---------|
+|`name`|String||Used when generating options; `stylix.targets.${name}`|"firefox"|
+|`humanName`|String||Used when generating enable option; `stylix.targets.${name}.enable` has a description of "Whether to enable theming for ${humanName}."|"Firefox"|
+|`autoEnable`|Boolean|true|Whether the target should be auto-enabled. Should be set to `false` if one of the following applies:<ul><li>The module requires further manual setup to work correctly.</li><li>There is no reliable way to detect whether the target is installed, *and* enabling it unconditionally would cause problems.</li></ul>|`false`|
+|`extraOptions`|Attrset of options|{ }|Attributes will be mapped to `stylix.targets.${name}.${option}`|`{ colorTheme.enable = lib.mkEnableOption "color theme"; }`|
+|`configElements`|List of or single (attrset or function returning attrset)|{ }|Function(s) should take the Stylix attributes that it needs (e.g. `colors`, `image`, `fonts`) and `cfg` if necessary. This `cfg` is an alias for `config.stylix.targets.${name}`. Function(s) should return an attribute set to be merged into `config`. If any of the Stylix attributes needed by a/the function are `null`, the configuration will not be merged.|*See above template*|
+|`generalConfig`|Null or function returning attrset|null|Same format as functions under `configElements`. Merged regardless of whether requested Stylix options are `null`. Avoid use when possible.|*See above template*|
path: docs/src/modules.md
user: login: Flameopathic
body: When Stylix options are optional, having them set as `null` is the default and equivalent to them being unset. When they are unset, all theming relating to them is disabled. Currently, there are only a few optional options, but our goal is to make all options optional. With this function, much of the legwork and repeated guards are handled for us.

See https://github.com/danth/stylix/discussions/1009#discussion-8091577 under **impetus** for more in-depth explanation. I have added two words to the description which hopefully give some clarification on the intention, but I will add more if you think it is necessary.
created_at: 2025-05-06T12:58:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -34,44 +34,47 @@ Other files needed by the module can also be stored within the
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`.
-This is similar to
-[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
-from the standard library, however it integrates with
-[`stylix.enable`](./options/nixos.md#stylixenable) and
-[`stylix.autoEnable`](./options/nixos.md#stylixautoenable)
-and generates more specific documentation.
+All modules should be created using `mkTarget`.
 
 A general format for modules is shown below.
 
 ```nix
+mkTarget:
 { config, lib, ... }:
+mkTarget
 {
-  options.stylix.targets.«name».enable =
-    config.lib.stylix.mkEnableTarget "«human readable name»" true;
+  name = "«target attribute name»";
+  humanName = "«human readable name»";
 
-  config =
-    lib.mkIf (config.stylix.enable && config.stylix.targets.«name».enable)
+  configElements = [
+    { colors }:
       {
         programs.«name».backgroundColor = config.lib.stylix.colors.base00;
       };
+  ];
 }
 ```
 
-The human readable name will be inserted into the following sentence:
+### `mkTarget`
 
-> Whether to enable theming for «human readable name».
+The `mkTarget` function is a special function which automatically generates
+options and guards configuration with`stylix.${attr} != null` on configuration
+to cut down on boilerplate.
 
-If your module will touch options outside of `programs.«name»` or `services.«name»`,
-it should include an additional condition in `mkIf` to prevent any effects
-when the target is not installed.
+Arguments without defaults must be set explicitly.
 
-The boolean value after `mkEnableTarget` should be changed to `false` if
-one of the following applies:
+| Argument | Type | Default | Description | Example |
+|----------|------|---------|-------------|---------|
+|`name`|String||Used when generating options; `stylix.targets.${name}`|"firefox"|
+|`humanName`|String||Used when generating enable option; `stylix.targets.${name}.enable` has a description of "Whether to enable theming for ${humanName}."|"Firefox"|
+|`autoEnable`|Boolean|true|Whether the target should be auto-enabled. Should be set to `false` if one of the following applies:<ul><li>The module requires further manual setup to work correctly.</li><li>There is no reliable way to detect whether the target is installed, *and* enabling it unconditionally would cause problems.</li></ul>|`false`|
+|`extraOptions`|Attrset of options|{ }|Attributes will be mapped to `stylix.targets.${name}.${option}`|`{ colorTheme.enable = lib.mkEnableOption "color theme"; }`|
path: docs/src/modules.md
user: login: Flameopathic
body: The reasoning behind the name was to not mislead contributors into thinking they have to make the `enable` option themselves. If you think this would not be a problem, I will change the name to `options`.
created_at: 2025-05-06T13:02:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -34,44 +34,47 @@ Other files needed by the module can also be stored within the
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`.
-This is similar to
-[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
-from the standard library, however it integrates with
-[`stylix.enable`](./options/nixos.md#stylixenable) and
-[`stylix.autoEnable`](./options/nixos.md#stylixautoenable)
-and generates more specific documentation.
+All modules should be created using `mkTarget`.
 
 A general format for modules is shown below.
 
 ```nix
+mkTarget:
 { config, lib, ... }:
+mkTarget
 {
-  options.stylix.targets.«name».enable =
-    config.lib.stylix.mkEnableTarget "«human readable name»" true;
+  name = "«target attribute name»";
+  humanName = "«human readable name»";
 
-  config =
-    lib.mkIf (config.stylix.enable && config.stylix.targets.«name».enable)
+  configElements = [
+    { colors }:
       {
         programs.«name».backgroundColor = config.lib.stylix.colors.base00;
       };
+  ];
 }
 ```
 
-The human readable name will be inserted into the following sentence:
+### `mkTarget`
 
-> Whether to enable theming for «human readable name».
+The `mkTarget` function is a special function which automatically generates
+options and guards configuration with`stylix.${attr} != null` on configuration
+to cut down on boilerplate.
 
-If your module will touch options outside of `programs.«name»` or `services.«name»`,
-it should include an additional condition in `mkIf` to prevent any effects
-when the target is not installed.
+Arguments without defaults must be set explicitly.
 
-The boolean value after `mkEnableTarget` should be changed to `false` if
-one of the following applies:
+| Argument | Type | Default | Description | Example |
+|----------|------|---------|-------------|---------|
+|`name`|String||Used when generating options; `stylix.targets.${name}`|"firefox"|
+|`humanName`|String||Used when generating enable option; `stylix.targets.${name}.enable` has a description of "Whether to enable theming for ${humanName}."|"Firefox"|
+|`autoEnable`|Boolean|true|Whether the target should be auto-enabled. Should be set to `false` if one of the following applies:<ul><li>The module requires further manual setup to work correctly.</li><li>There is no reliable way to detect whether the target is installed, *and* enabling it unconditionally would cause problems.</li></ul>|`false`|
+|`extraOptions`|Attrset of options|{ }|Attributes will be mapped to `stylix.targets.${name}.${option}`|`{ colorTheme.enable = lib.mkEnableOption "color theme"; }`|
+|`configElements`|List of or single (attrset or function returning attrset)|{ }|Function(s) should take the Stylix attributes that it needs (e.g. `colors`, `image`, `fonts`) and `cfg` if necessary. This `cfg` is an alias for `config.stylix.targets.${name}`. Function(s) should return an attribute set to be merged into `config`. If any of the Stylix attributes needed by a/the function are `null`, the configuration will not be merged.|*See above template*|
+|`generalConfig`|Null or function returning attrset|null|Same format as functions under `configElements`. Merged regardless of whether requested Stylix options are `null`. Avoid use when possible.|*See above template*|
path: docs/src/modules.md
user: login: MattSturgeon
body: Thanks for explaining.

Would something like this work?

> The configuration will not be used if any of the attributes required by the function are `null`; i.e., if the Stylix option is disabled.
created_at: 2025-05-06T13:33:54Z
author_association: MEMBER
diff_hunk: @@ -34,44 +34,47 @@ Other files needed by the module can also be stored within the
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`.
-This is similar to
-[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
-from the standard library, however it integrates with
-[`stylix.enable`](./options/nixos.md#stylixenable) and
-[`stylix.autoEnable`](./options/nixos.md#stylixautoenable)
-and generates more specific documentation.
+All modules should be created using `mkTarget`.
 
 A general format for modules is shown below.
 
 ```nix
+mkTarget:
 { config, lib, ... }:
+mkTarget
 {
-  options.stylix.targets.«name».enable =
-    config.lib.stylix.mkEnableTarget "«human readable name»" true;
+  name = "«target attribute name»";
+  humanName = "«human readable name»";
 
-  config =
-    lib.mkIf (config.stylix.enable && config.stylix.targets.«name».enable)
+  configElements = [
+    { colors }:
       {
         programs.«name».backgroundColor = config.lib.stylix.colors.base00;
       };
+  ];
 }
 ```
 
-The human readable name will be inserted into the following sentence:
+### `mkTarget`
 
-> Whether to enable theming for «human readable name».
+The `mkTarget` function is a special function which automatically generates
+options and guards configuration with`stylix.${attr} != null` on configuration
+to cut down on boilerplate.
 
-If your module will touch options outside of `programs.«name»` or `services.«name»`,
-it should include an additional condition in `mkIf` to prevent any effects
-when the target is not installed.
+Arguments without defaults must be set explicitly.
 
-The boolean value after `mkEnableTarget` should be changed to `false` if
-one of the following applies:
+| Argument | Type | Default | Description | Example |
+|----------|------|---------|-------------|---------|
+|`name`|String||Used when generating options; `stylix.targets.${name}`|"firefox"|
+|`humanName`|String||Used when generating enable option; `stylix.targets.${name}.enable` has a description of "Whether to enable theming for ${humanName}."|"Firefox"|
+|`autoEnable`|Boolean|true|Whether the target should be auto-enabled. Should be set to `false` if one of the following applies:<ul><li>The module requires further manual setup to work correctly.</li><li>There is no reliable way to detect whether the target is installed, *and* enabling it unconditionally would cause problems.</li></ul>|`false`|
+|`extraOptions`|Attrset of options|{ }|Attributes will be mapped to `stylix.targets.${name}.${option}`|`{ colorTheme.enable = lib.mkEnableOption "color theme"; }`|
path: docs/src/modules.md
user: login: MattSturgeon
body: > not mislead contributors into thinking they have to make the enable option themselves.

Makes sense. In that case, I agree with avoiding `options`.

Something like `extraOptions`, `targetOptions`, etc is clearer 👍
created_at: 2025-05-06T13:35:40Z
author_association: MEMBER
diff_hunk: @@ -34,44 +34,47 @@ Other files needed by the module can also be stored within the
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`.
-This is similar to
-[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
-from the standard library, however it integrates with
-[`stylix.enable`](./options/nixos.md#stylixenable) and
-[`stylix.autoEnable`](./options/nixos.md#stylixautoenable)
-and generates more specific documentation.
+All modules should be created using `mkTarget`.
 
 A general format for modules is shown below.
 
 ```nix
+mkTarget:
 { config, lib, ... }:
path: docs/src/modules.md
user: login: MattSturgeon
body: I agree _this_ example doesn't need module args. But demonstrating they can be used is useful.

Maybe another example could demonstrate them by using `lib` for declaring an option or something?
created_at: 2025-05-06T13:38:24Z
author_association: MEMBER
diff_hunk: @@ -34,44 +34,47 @@ Other files needed by the module can also be stored within the
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`.
-This is similar to
-[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
-from the standard library, however it integrates with
-[`stylix.enable`](./options/nixos.md#stylixenable) and
-[`stylix.autoEnable`](./options/nixos.md#stylixautoenable)
-and generates more specific documentation.
+All modules should be created using `mkTarget`.
 
 A general format for modules is shown below.
 
 ```nix
+mkTarget:
 { config, lib, ... }:
path: docs/src/modules.md
user: login: Flameopathic
body: I added a note about this. I would like to keep it there, even though it is unnecessary, for the purposes of demonstration.
created_at: 2025-05-06T15:32:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,93 @@
+# See docs/src/modules.md#mkTarget for full explanation
+{
+  # Used when generating options; `stylix.targets.${name}`
+  name,
+
+  # Used when generating enable option; `stylix.targets.${name}.enable` has a
+  # description of "Whether to enable theming for ${humanName}."
+  humanName,
+
+  # Whether the target should be auto-enabled
+  autoEnable ? true,
+
+  # Attributes will be mapped to `stylix.targets.${name}.${option}`
+  extraOptions ? { },
+
+  # Function(s) should take the Stylix attributes that it needs (e.g. `colors`,
+  # `image`, `fonts`) and `cfg` if necessary. This `cfg` is an alias for
+  # `config.stylix.targets.${name}`. Function(s) should return an attribute set
+  # to be merged into `config`. If any of the Stylix attributes needed by a/the
+  # function are `null`, AKA unset, the configuration will not be merged.
+  configElements ? [ ],
+
+  # Same format as functions under `configElements`. Merged regardless of whether
+  # requested Stylix options are `null`. Avoid use when possible.
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args, while using this as their module body
+let
+  module =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+
+      # Get the function's de-structured arg names as a list
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+
+      # Get the Stylix attrs (option values) requested by the function's de-structured args
+      getStylixAttrs =
+        fn:
+        lib.genAttrs (functionArgNames fn) (
+          arg:
+          if arg == "cfg" then
+            cfg
+          else if arg == "colors" then
+            config.lib.stylix.colors
+          else
+            config.stylix.${arg}
+              or (throw "stylix: Stylix arg was requested by mk-target but does not exist: ${arg}")
+        );
+
+      # Call the config element function with the requested stylix attr values
+      # Returns a config definition
+      mkConfig = fn: fn (getStylixAttrs fn);
+
+      # Wrap the config returned by the config element with the appropriate `mkIf` condition
+      # If the config element is a function, call it using `mkConfig` (above)
+      # Returns a config definition wrapped with `mkIf`
+      mkConditionalConfig =
+        c:
+        if builtins.isFunction c then
+          let
+            attrs = builtins.attrValues (getStylixAttrs c);
+            allAttrsNonNull = builtins.all (attr: attr != null) attrs;
path: stylix/mk-target.nix
user: login: MattSturgeon
body: ```suggestion
            allAttrsNonNull = builtins.all (v: v != null) (builtins.attrValues attrs);
```

`attrs` is a set right? `all` expects a list.

Sorry I didn't spot this in earlier feedback: is this code path not getting picked up by CI?

created_at: 2025-05-06T16:17:28Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,93 @@
+# See docs/src/modules.md#mkTarget for full explanation
+{
+  # Used when generating options; `stylix.targets.${name}`
+  name,
+
+  # Used when generating enable option; `stylix.targets.${name}.enable` has a
+  # description of "Whether to enable theming for ${humanName}."
+  humanName,
+
+  # Whether the target should be auto-enabled
+  autoEnable ? true,
+
+  # Attributes will be mapped to `stylix.targets.${name}.${option}`
+  extraOptions ? { },
+
+  # Function(s) should take the Stylix attributes that it needs (e.g. `colors`,
+  # `image`, `fonts`) and `cfg` if necessary. This `cfg` is an alias for
+  # `config.stylix.targets.${name}`. Function(s) should return an attribute set
+  # to be merged into `config`. If any of the Stylix attributes needed by a/the
+  # function are `null`, AKA unset, the configuration will not be merged.
+  configElements ? [ ],
+
+  # Same format as functions under `configElements`. Merged regardless of whether
+  # requested Stylix options are `null`. Avoid use when possible.
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args, while using this as their module body
+let
+  module =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+
+      # Get the function's de-structured arg names as a list
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+
+      # Get the Stylix attrs (option values) requested by the function's de-structured args
+      getStylixAttrs =
+        fn:
+        lib.genAttrs (functionArgNames fn) (
+          arg:
+          if arg == "cfg" then
+            cfg
+          else if arg == "colors" then
+            config.lib.stylix.colors
+          else
+            config.stylix.${arg}
+              or (throw "stylix: Stylix arg was requested by mk-target but does not exist: ${arg}")
+        );
+
+      # Call the config element function with the requested stylix attr values
+      # Returns a config definition
+      mkConfig = fn: fn (getStylixAttrs fn);
+
+      # Wrap the config returned by the config element with the appropriate `mkIf` condition
+      # If the config element is a function, call it using `mkConfig` (above)
+      # Returns a config definition wrapped with `mkIf`
+      mkConditionalConfig =
+        c:
+        if builtins.isFunction c then
+          let
+            attrs = builtins.attrValues (getStylixAttrs c);
+            allAttrsNonNull = builtins.all (attr: attr != null) attrs;
path: stylix/mk-target.nix
user: login: Flameopathic
body: Another naming mistake, it is a list of the values.
created_at: 2025-05-06T16:22:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,93 @@
+# See docs/src/modules.md#mkTarget for full explanation
+{
+  # Used when generating options; `stylix.targets.${name}`
+  name,
+
+  # Used when generating enable option; `stylix.targets.${name}.enable` has a
+  # description of "Whether to enable theming for ${humanName}."
+  humanName,
+
+  # Whether the target should be auto-enabled
+  autoEnable ? true,
+
+  # Attributes will be mapped to `stylix.targets.${name}.${option}`
+  extraOptions ? { },
+
+  # Function(s) should take the Stylix attributes that it needs (e.g. `colors`,
+  # `image`, `fonts`) and `cfg` if necessary. This `cfg` is an alias for
+  # `config.stylix.targets.${name}`. Function(s) should return an attribute set
+  # to be merged into `config`. If any of the Stylix attributes needed by a/the
+  # function are `null`, AKA unset, the configuration will not be merged.
+  configElements ? [ ],
+
+  # Same format as functions under `configElements`. Merged regardless of whether
+  # requested Stylix options are `null`. Avoid use when possible.
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args, while using this as their module body
+let
+  module =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+
+      # Get the function's de-structured arg names as a list
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+
+      # Get the Stylix attrs (option values) requested by the function's de-structured args
+      getStylixAttrs =
+        fn:
+        lib.genAttrs (functionArgNames fn) (
+          arg:
+          if arg == "cfg" then
+            cfg
+          else if arg == "colors" then
+            config.lib.stylix.colors
+          else
+            config.stylix.${arg}
+              or (throw "stylix: Stylix arg was requested by mk-target but does not exist: ${arg}")
+        );
+
+      # Call the config element function with the requested stylix attr values
+      # Returns a config definition
+      mkConfig = fn: fn (getStylixAttrs fn);
+
+      # Wrap the config returned by the config element with the appropriate `mkIf` condition
+      # If the config element is a function, call it using `mkConfig` (above)
+      # Returns a config definition wrapped with `mkIf`
+      mkConditionalConfig =
+        c:
+        if builtins.isFunction c then
+          let
+            attrs = builtins.attrValues (getStylixAttrs c);
+            allAttrsNonNull = builtins.all (attr: attr != null) attrs;
path: stylix/mk-target.nix
user: login: MattSturgeon
body: Ah of course, my mistake 😅
created_at: 2025-05-06T17:13:50Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,93 @@
+# See docs/src/modules.md#mkTarget for full explanation
+{
+  # Used when generating options; `stylix.targets.${name}`
+  name,
+
+  # Used when generating enable option; `stylix.targets.${name}.enable` has a
+  # description of "Whether to enable theming for ${humanName}."
+  humanName,
+
+  # Whether the target should be auto-enabled
+  autoEnable ? true,
+
+  # Attributes will be mapped to `stylix.targets.${name}.${option}`
+  extraOptions ? { },
+
+  # Function(s) should take the Stylix attributes that it needs (e.g. `colors`,
+  # `image`, `fonts`) and `cfg` if necessary. This `cfg` is an alias for
+  # `config.stylix.targets.${name}`. Function(s) should return an attribute set
+  # to be merged into `config`. If any of the Stylix attributes needed by a/the
+  # function are `null`, AKA unset, the configuration will not be merged.
+  configElements ? [ ],
+
+  # Same format as functions under `configElements`. Merged regardless of whether
+  # requested Stylix options are `null`. Avoid use when possible.
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args, while using this as their module body
+let
+  module =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+
+      # Get the function's de-structured arg names as a list
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+
+      # Get the Stylix attrs (option values) requested by the function's de-structured args
+      getStylixAttrs =
+        fn:
+        lib.genAttrs (functionArgNames fn) (
+          arg:
+          if arg == "cfg" then
+            cfg
+          else if arg == "colors" then
+            config.lib.stylix.colors
+          else
+            config.stylix.${arg}
+              or (throw "stylix: Stylix arg was requested by mk-target but does not exist: ${arg}")
+        );
+
+      # Call the config element function with the requested stylix attr values
+      # Returns a config definition
+      mkConfig = fn: fn (getStylixAttrs fn);
+
+      # Wrap the config returned by the config element with the appropriate `mkIf` condition
+      # If the config element is a function, call it using `mkConfig` (above)
+      # Returns a config definition wrapped with `mkIf`
+      mkConditionalConfig =
+        c:
+        if builtins.isFunction c then
+          let
+            attrs = builtins.attrValues (getStylixAttrs c);
+            allAttrsNonNull = builtins.all (attr: attr != null) attrs;
path: stylix/mk-target.nix
user: login: MattSturgeon
body: Would this be clearer with a pipe?


```nix
allAttrsNonNull = lib.pipe c [
  getStylixAttrs
  builtins.attrValues
  (builtins.all (attr: attr != null))
]
```

created_at: 2025-05-06T21:46:57Z
author_association: MEMBER
diff_hunk: @@ -8,7 +8,20 @@ builtins.concatLists (
     path: kind:
     let
       file = "${inputs.self}/modules/${path}/${for}.nix";
+      module = import file;
+      # Assume that a function-module whose first arg doesn't have any de-
+      # structured attrs is actually expecting `mkTarget` as its first arg;
+      #
+      # `mkTarget` cannot be distribtued normally through the module system
+      # due to issues of infinite recursion
+      expectsMkTarget =
+        builtins.isFunction module && builtins.functionArgs module == { };
     in
-    lib.optional (kind == "directory" && builtins.pathExists file) file
+    lib.optional (kind == "directory" && builtins.pathExists file) (
+      if expectsMkTarget then
+        lib.modules.importApply file (import ./mk-target.nix)
path: stylix/autoload.nix
user: login: MattSturgeon
body: I was just re-reading the [docs for `importApply`](https://github.com/NixOS/nixpkgs/blob/b22909a5fe8490fa06451718cf3224eea506dac8/lib/modules.nix#L1882-L1887) and noticed a detail I'd missed before:

> This function does not add support for deduplication and `disabledModules`, although that could be achieved by wrapping the returned module and setting the `_key` module attribute.
> 
> The reason for this omission is that the file path is not guaranteed to be a unique identifier for the module, as two instances of the module may reference different `arg`s in their closures.

This contradicts what I said in https://github.com/danth/stylix/pull/1185#issuecomment-2848319802 and https://github.com/danth/stylix/pull/1185#issuecomment-2849481858.

It is also the first I've heard of the `_key` attribute. It sounds similar to the `_file` attribute that _is_ set by `importApply`.

I'd need to look into `disabledModules` and `_key` a bit more, but perhaps the correct approach here is:


```suggestion
        {
          _key = file;
          _file = file;
          imports = [ (module mkTarget) ];
        }
```

?
created_at: 2025-05-07T22:40:29Z
author_association: MEMBER
diff_hunk: @@ -8,7 +8,20 @@ builtins.concatLists (
     path: kind:
     let
       file = "${inputs.self}/modules/${path}/${for}.nix";
+      module = import file;
+      # Assume that a function-module whose first arg doesn't have any de-
+      # structured attrs is actually expecting `mkTarget` as its first arg;
+      #
+      # `mkTarget` cannot be distribtued normally through the module system
+      # due to issues of infinite recursion
+      expectsMkTarget =
+        builtins.isFunction module && builtins.functionArgs module == { };
     in
-    lib.optional (kind == "directory" && builtins.pathExists file) file
+    lib.optional (kind == "directory" && builtins.pathExists file) (
+      if expectsMkTarget then
+        lib.modules.importApply file (import ./mk-target.nix)
path: stylix/autoload.nix
user: login: Flameopathic
body: This is a little above my pay grade... I'll put it in and we'll see CI's response.
created_at: 2025-05-07T23:51:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -8,7 +8,20 @@ builtins.concatLists (
     path: kind:
     let
       file = "${inputs.self}/modules/${path}/${for}.nix";
+      module = import file;
+      # Assume that a function-module whose first arg doesn't have any de-
+      # structured attrs is actually expecting `mkTarget` as its first arg;
+      #
+      # `mkTarget` cannot be distribtued normally through the module system
+      # due to issues of infinite recursion
+      expectsMkTarget =
+        builtins.isFunction module && builtins.functionArgs module == { };
     in
-    lib.optional (kind == "directory" && builtins.pathExists file) file
+    lib.optional (kind == "directory" && builtins.pathExists file) (
+      if expectsMkTarget then
+        lib.modules.importApply file (import ./mk-target.nix)
path: stylix/autoload.nix
user: login: MattSturgeon
body: Looking at the [impl in `collectModules`](https://github.com/NixOS/nixpkgs/blob/b22909a5fe8490fa06451718cf3224eea506dac8/lib/modules.nix#L486-L519), I think the `importApply` docs may be slightly off; I believe it is `key` not `_key`.
created_at: 2025-05-08T00:12:43Z
author_association: MEMBER
diff_hunk: @@ -34,15 +34,40 @@ Other files needed by the module can also be stored within the
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`.
+Modules should be created using `mkTarget` whenever possible.
+
+When using `mkTarget`, request it as a non-destructured arg before the optional
+module args. See the below example.
path: docs/src/modules.md
user: login: MattSturgeon
body: Since you can't de-structure[^1] a function (unless it's a functor[^2]), it is redundant to mention de-structuring. Doing so in docs intended for new/external contributors will probably cause more confusion than clarity:

```suggestion
When using `mkTarget`, request it _before_ the optional module args.
See the below example.
```

[^1]: in the [functions section](https://nix.dev/manual/nix/2.28/language/syntax#functions) of the manual, they refer to de-structured args as a _"set pattern"_
[^2]: a functor is a callable attrset, see the [attrset section](https://nix.dev/manual/nix/2.28/language/syntax#attr-__functor) of the manual
created_at: 2025-05-11T18:38:49Z
author_association: MEMBER
diff_hunk: @@ -34,15 +34,40 @@ Other files needed by the module can also be stored within the
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`.
+Modules should be created using `mkTarget` whenever possible.
+
+When using `mkTarget`, request it as a non-destructured arg before the optional
+module args. See the below example.
+
+When impossible, modules should have an enable option created using `mkEnableTarget`.
path: docs/src/modules.md
user: login: MattSturgeon
body: ```suggestion
When `mkTarget` cannot be used, modules should declare an `enable` option using `mkEnableTarget`
and guard their config definitions behind the appropriate `mkIf` conditions.
```
created_at: 2025-05-11T18:41:17Z
author_association: MEMBER
diff_hunk: @@ -34,15 +34,40 @@ Other files needed by the module can also be stored within the
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`.
+Modules should be created using `mkTarget` whenever possible.
+
+When using `mkTarget`, request it as a non-destructured arg before the optional
+module args. See the below example.
+
+When impossible, modules should have an enable option created using `mkEnableTarget`.
 This is similar to
 [`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
 from the standard library, however it integrates with
 [`stylix.enable`](./options/nixos.md#stylixenable) and
 [`stylix.autoEnable`](./options/nixos.md#stylixautoenable)
 and generates more specific documentation.
 
-A general format for modules is shown below.
+General formats for modules are shown below.
+
+Using `mkTarget`:
+
+```nix
+mkTarget:
+{ config, lib, ... }: # this line for demonstration purposes only
path: docs/src/modules.md
user: login: MattSturgeon
body: ```suggestion
{ config, lib, ... }: # optional module args, after `mkTarget`
created_at: 2025-05-11T18:41:53Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,96 @@
+# See docs/src/modules.md#mkTarget for full explanation
+{
+  # Used when generating options; `stylix.targets.${name}`
+  name,
+
+  # Used when generating enable option; `stylix.targets.${name}.enable` has a
+  # description of "Whether to enable theming for ${humanName}."
+  humanName,
+
+  # Whether the target should be auto-enabled
+  autoEnable ? true,
+
+  # Attributes will be mapped to `stylix.targets.${name}.${option}`
+  extraOptions ? { },
+
+  # Function(s) should take the Stylix attributes that it needs (e.g. `colors`,
+  # `image`, `fonts`) and `cfg` if necessary. This `cfg` is an alias for
+  # `config.stylix.targets.${name}`. Function(s) should return an attribute set
+  # to be merged into `config`. If any of the Stylix attributes needed by a/the
+  # function are `null`, AKA unset, the configuration will not be merged.
+  configElements ? [ ],
+
+  # Same format as functions under `configElements`. Merged regardless of whether
+  # requested Stylix options are `null`. Avoid use when possible.
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args, while using this as their module body
+let
+  module =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+
+      # Get the function's de-structured arg names as a list
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+
+      # Get the Stylix attrs (option values) requested by the function's de-structured args
+      getStylixAttrs =
+        fn:
+        lib.genAttrs (functionArgNames fn) (
+          arg:
+          if arg == "cfg" then
+            cfg
+          else if arg == "colors" then
+            config.lib.stylix.colors
+          else
+            config.stylix.${arg}
+              or (throw "stylix: Stylix arg was requested by mk-target but does not exist: ${arg}")
+        );
+
+      # Call the config element function with the requested stylix attr values
+      # Returns a config definition
+      mkConfig = fn: fn (getStylixAttrs fn);
+
+      # Wrap the config returned by the config element with the appropriate `mkIf` condition
+      # If the config element is a function, call it using `mkConfig` (above)
+      # Returns a config definition wrapped with `mkIf`
+      mkConditionalConfig =
+        c:
+        if builtins.isFunction c then
+          let
+            allAttrsNonNull = lib.pipe c [
+              getStylixAttrs
+              builtins.attrValues
+              (builtins.all (attr: attr != null))
+            ];
+          in
+          lib.mkIf allAttrsNonNull (mkConfig c)
+        else
+          c;
+    in
+    {
+      options.stylix.targets.${name}.enable =
+        config.lib.stylix.mkEnableTarget humanName autoEnable;
+
+      config = lib.mkIf (config.stylix.enable && cfg.enable) (
+        lib.mkMerge (
+          lib.optional (generalConfig != null) (mkConfig generalConfig)
+          ++ map mkConditionalConfig (lib.toList configElements)
+        )
+      );
+    };
+in
+{
+  imports = [
+    {
+      options.stylix.targets.${name} = extraOptions;
+    }
path: stylix/mk-target.nix
user: login: trueNAHO
body: ```suggestion
    { options.stylix.targets.${name} = extraOptions; }
```

created_at: 2025-05-13T14:41:22Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,96 @@
+# See docs/src/modules.md#mkTarget for full explanation
+{
+  # Used when generating options; `stylix.targets.${name}`
+  name,
+
+  # Used when generating enable option; `stylix.targets.${name}.enable` has a
+  # description of "Whether to enable theming for ${humanName}."
+  humanName,
+
+  # Whether the target should be auto-enabled
+  autoEnable ? true,
+
+  # Attributes will be mapped to `stylix.targets.${name}.${option}`
+  extraOptions ? { },
+
+  # Function(s) should take the Stylix attributes that it needs (e.g. `colors`,
+  # `image`, `fonts`) and `cfg` if necessary. This `cfg` is an alias for
+  # `config.stylix.targets.${name}`. Function(s) should return an attribute set
+  # to be merged into `config`. If any of the Stylix attributes needed by a/the
+  # function are `null`, AKA unset, the configuration will not be merged.
+  configElements ? [ ],
+
+  # Same format as functions under `configElements`. Merged regardless of whether
+  # requested Stylix options are `null`. Avoid use when possible.
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args, while using this as their module body
+let
+  module =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+
+      # Get the function's de-structured arg names as a list
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+
+      # Get the Stylix attrs (option values) requested by the function's de-structured args
+      getStylixAttrs =
+        fn:
+        lib.genAttrs (functionArgNames fn) (
+          arg:
+          if arg == "cfg" then
+            cfg
path: stylix/mk-target.nix
user: login: trueNAHO
body: It might be better to disallow `cfg` overriding to ensure `cfg = config.stylix.targets.${name};` consistency by `throw`ing an error.

created_at: 2025-05-13T14:46:41Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,96 @@
+# See docs/src/modules.md#mkTarget for full explanation
+{
+  # Used when generating options; `stylix.targets.${name}`
+  name,
+
+  # Used when generating enable option; `stylix.targets.${name}.enable` has a
+  # description of "Whether to enable theming for ${humanName}."
+  humanName,
+
+  # Whether the target should be auto-enabled
+  autoEnable ? true,
+
+  # Attributes will be mapped to `stylix.targets.${name}.${option}`
+  extraOptions ? { },
+
+  # Function(s) should take the Stylix attributes that it needs (e.g. `colors`,
+  # `image`, `fonts`) and `cfg` if necessary. This `cfg` is an alias for
+  # `config.stylix.targets.${name}`. Function(s) should return an attribute set
+  # to be merged into `config`. If any of the Stylix attributes needed by a/the
+  # function are `null`, AKA unset, the configuration will not be merged.
+  configElements ? [ ],
+
+  # Same format as functions under `configElements`. Merged regardless of whether
+  # requested Stylix options are `null`. Avoid use when possible.
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args, while using this as their module body
+let
+  module =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+
+      # Get the function's de-structured arg names as a list
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+
+      # Get the Stylix attrs (option values) requested by the function's de-structured args
+      getStylixAttrs =
+        fn:
+        lib.genAttrs (functionArgNames fn) (
+          arg:
+          if arg == "cfg" then
+            cfg
+          else if arg == "colors" then
+            config.lib.stylix.colors
+          else
+            config.stylix.${arg}
+              or (throw "stylix: Stylix arg was requested by mk-target but does not exist: ${arg}")
path: stylix/mk-target.nix
user: login: trueNAHO
body: ```suggestion
              or (throw "stylix: Stylix argument requested by mkTarget does not exist: ${arg}")
```

created_at: 2025-05-13T14:48:00Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,96 @@
+# See docs/src/modules.md#mkTarget for full explanation
+{
+  # Used when generating options; `stylix.targets.${name}`
+  name,
+
+  # Used when generating enable option; `stylix.targets.${name}.enable` has a
+  # description of "Whether to enable theming for ${humanName}."
+  humanName,
+
+  # Whether the target should be auto-enabled
+  autoEnable ? true,
+
+  # Attributes will be mapped to `stylix.targets.${name}.${option}`
+  extraOptions ? { },
+
+  # Function(s) should take the Stylix attributes that it needs (e.g. `colors`,
+  # `image`, `fonts`) and `cfg` if necessary. This `cfg` is an alias for
+  # `config.stylix.targets.${name}`. Function(s) should return an attribute set
+  # to be merged into `config`. If any of the Stylix attributes needed by a/the
+  # function are `null`, AKA unset, the configuration will not be merged.
+  configElements ? [ ],
+
+  # Same format as functions under `configElements`. Merged regardless of whether
+  # requested Stylix options are `null`. Avoid use when possible.
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args, while using this as their module body
+let
+  module =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+
+      # Get the function's de-structured arg names as a list
path: stylix/mk-target.nix
user: login: trueNAHO
body: This here should flow better:

```suggestion
      # Get the list of function de-structured argument names.
```

This comment seems somewhat obvious, considering the very simple function implementation. Should we drop this comment?

created_at: 2025-05-13T14:50:15Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,96 @@
+# See docs/src/modules.md#mkTarget for full explanation
+{
+  # Used when generating options; `stylix.targets.${name}`
+  name,
+
+  # Used when generating enable option; `stylix.targets.${name}.enable` has a
+  # description of "Whether to enable theming for ${humanName}."
+  humanName,
+
+  # Whether the target should be auto-enabled
+  autoEnable ? true,
+
+  # Attributes will be mapped to `stylix.targets.${name}.${option}`
+  extraOptions ? { },
+
+  # Function(s) should take the Stylix attributes that it needs (e.g. `colors`,
+  # `image`, `fonts`) and `cfg` if necessary. This `cfg` is an alias for
+  # `config.stylix.targets.${name}`. Function(s) should return an attribute set
+  # to be merged into `config`. If any of the Stylix attributes needed by a/the
+  # function are `null`, AKA unset, the configuration will not be merged.
+  configElements ? [ ],
+
+  # Same format as functions under `configElements`. Merged regardless of whether
+  # requested Stylix options are `null`. Avoid use when possible.
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args, while using this as their module body
+let
+  module =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+
+      # Get the function's de-structured arg names as a list
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+
+      # Get the Stylix attrs (option values) requested by the function's de-structured args
path: stylix/mk-target.nix
user: login: trueNAHO
body: I think it would be better if the function does not mention where the caller receives its values from in order to keep the function more standalone. In that case, the comment seems redundant with the `getStylixAttrs` function name:

```suggestion
```

created_at: 2025-05-13T14:52:59Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,96 @@
+# See docs/src/modules.md#mkTarget for full explanation
+{
+  # Used when generating options; `stylix.targets.${name}`
+  name,
+
+  # Used when generating enable option; `stylix.targets.${name}.enable` has a
+  # description of "Whether to enable theming for ${humanName}."
+  humanName,
+
+  # Whether the target should be auto-enabled
+  autoEnable ? true,
+
+  # Attributes will be mapped to `stylix.targets.${name}.${option}`
+  extraOptions ? { },
+
+  # Function(s) should take the Stylix attributes that it needs (e.g. `colors`,
+  # `image`, `fonts`) and `cfg` if necessary. This `cfg` is an alias for
+  # `config.stylix.targets.${name}`. Function(s) should return an attribute set
+  # to be merged into `config`. If any of the Stylix attributes needed by a/the
+  # function are `null`, AKA unset, the configuration will not be merged.
+  configElements ? [ ],
+
+  # Same format as functions under `configElements`. Merged regardless of whether
+  # requested Stylix options are `null`. Avoid use when possible.
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args, while using this as their module body
+let
+  module =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+
+      # Get the function's de-structured arg names as a list
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+
+      # Get the Stylix attrs (option values) requested by the function's de-structured args
+      getStylixAttrs =
+        fn:
+        lib.genAttrs (functionArgNames fn) (
+          arg:
+          if arg == "cfg" then
+            cfg
+          else if arg == "colors" then
+            config.lib.stylix.colors
+          else
+            config.stylix.${arg}
+              or (throw "stylix: Stylix arg was requested by mk-target but does not exist: ${arg}")
+        );
+
+      # Call the config element function with the requested stylix attr values
+      # Returns a config definition
+      mkConfig = fn: fn (getStylixAttrs fn);
+
+      # Wrap the config returned by the config element with the appropriate `mkIf` condition
+      # If the config element is a function, call it using `mkConfig` (above)
+      # Returns a config definition wrapped with `mkIf`
path: stylix/mk-target.nix
user: login: trueNAHO
body: Considering the file header from my "[PATCH 1/2] doc: centralize mkTarget documentation" patch, the following should be sufficient:

```suggestion
      # Safeguard configuration functions when any of their arguments is
      # disabled, while non-function configurations are unguarded.
```

created_at: 2025-05-13T15:01:02Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,96 @@
+# See docs/src/modules.md#mkTarget for full explanation
+{
+  # Used when generating options; `stylix.targets.${name}`
+  name,
+
+  # Used when generating enable option; `stylix.targets.${name}.enable` has a
+  # description of "Whether to enable theming for ${humanName}."
+  humanName,
+
+  # Whether the target should be auto-enabled
+  autoEnable ? true,
+
+  # Attributes will be mapped to `stylix.targets.${name}.${option}`
+  extraOptions ? { },
+
+  # Function(s) should take the Stylix attributes that it needs (e.g. `colors`,
+  # `image`, `fonts`) and `cfg` if necessary. This `cfg` is an alias for
+  # `config.stylix.targets.${name}`. Function(s) should return an attribute set
+  # to be merged into `config`. If any of the Stylix attributes needed by a/the
+  # function are `null`, AKA unset, the configuration will not be merged.
+  configElements ? [ ],
+
+  # Same format as functions under `configElements`. Merged regardless of whether
+  # requested Stylix options are `null`. Avoid use when possible.
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args, while using this as their module body
+let
+  module =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+
+      # Get the function's de-structured arg names as a list
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+
+      # Get the Stylix attrs (option values) requested by the function's de-structured args
+      getStylixAttrs =
+        fn:
+        lib.genAttrs (functionArgNames fn) (
+          arg:
+          if arg == "cfg" then
+            cfg
+          else if arg == "colors" then
+            config.lib.stylix.colors
+          else
+            config.stylix.${arg}
+              or (throw "stylix: Stylix arg was requested by mk-target but does not exist: ${arg}")
+        );
+
+      # Call the config element function with the requested stylix attr values
+      # Returns a config definition
path: stylix/mk-target.nix
user: login: trueNAHO
body: I think the implementation "detail" of specifically requesting Stylix arguments is too specific for the function documentation:

```suggestion
      # Call the configuration function with its required arguments.
```

created_at: 2025-05-13T15:22:26Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,96 @@
+# See docs/src/modules.md#mkTarget for full explanation
+{
+  # Used when generating options; `stylix.targets.${name}`
+  name,
+
+  # Used when generating enable option; `stylix.targets.${name}.enable` has a
+  # description of "Whether to enable theming for ${humanName}."
+  humanName,
+
+  # Whether the target should be auto-enabled
+  autoEnable ? true,
+
+  # Attributes will be mapped to `stylix.targets.${name}.${option}`
+  extraOptions ? { },
+
+  # Function(s) should take the Stylix attributes that it needs (e.g. `colors`,
+  # `image`, `fonts`) and `cfg` if necessary. This `cfg` is an alias for
+  # `config.stylix.targets.${name}`. Function(s) should return an attribute set
+  # to be merged into `config`. If any of the Stylix attributes needed by a/the
+  # function are `null`, AKA unset, the configuration will not be merged.
+  configElements ? [ ],
+
+  # Same format as functions under `configElements`. Merged regardless of whether
+  # requested Stylix options are `null`. Avoid use when possible.
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args, while using this as their module body
+let
+  module =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+
+      # Get the function's de-structured arg names as a list
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+
+      # Get the Stylix attrs (option values) requested by the function's de-structured args
+      getStylixAttrs =
+        fn:
+        lib.genAttrs (functionArgNames fn) (
+          arg:
+          if arg == "cfg" then
+            cfg
+          else if arg == "colors" then
+            config.lib.stylix.colors
+          else
+            config.stylix.${arg}
+              or (throw "stylix: Stylix arg was requested by mk-target but does not exist: ${arg}")
+        );
+
+      # Call the config element function with the requested stylix attr values
+      # Returns a config definition
path: stylix/mk-target.nix
user: login: MattSturgeon
body: > too specific for the function documentation

Note that `#` and `/* */` comments are not documentation, they are normal comments intended to explain why something is done, clarify impl details, etc.

i.e. notes to people hacking on `mkTarget`, not notes for people using `mkTarget`.

Documentation should go in a doc-comment `/** */` and/or markdown files.
created_at: 2025-05-13T15:49:42Z
author_association: MEMBER
diff_hunk: @@ -8,7 +8,24 @@ builtins.concatLists (
     path: kind:
     let
       file = "${inputs.self}/modules/${path}/${for}.nix";
+      module = import file;
+      # Assume that a function-module whose first arg doesn't have any de-
+      # structured attrs is actually expecting `mkTarget` as its first arg;
+      #
+      # `mkTarget` cannot be distribtued normally through the module system
+      # due to issues of infinite recursion
+      expectsMkTarget =
+        builtins.isFunction module && builtins.functionArgs module == { };
path: stylix/autoload.nix
user: login: MattSturgeon
body: Following up on @trueNAHO's idea of `/modules/<MODULE>/<PLATFORM>.nix` files being "args for mkTarget" instead of actual modules:

> Ideally, in the future, this function returns an actual module by better integrating with the `/stylix/autoload.nix` logic, allowing the following target simplification and preventing access to unguarded module arguments by requiring `/modules/<MODULE>/<PLATFORM>.nix` files to be attribute sets instead of modules:

While it's a big shift for the design of the PR, I do like this more. Especially if mkTarget-files can reliably indicate that they are not modules, e.g. via the presence of some attr like `humanName`.

```nix
path: kind:
let
  file = "${inputs.self}/modules/${path}/${for}.nix";
  value = import file;

  # Detect whether the file's value is a module or arguments for `mkTarget`
  #
  # NOTE: `humanName` is a required arg for mkTarget, and is not expected in
  # any modules.
  #
  # NOTE: `mkTarget` cannot be distributed normally through the module system
  # due to issues of infinite recursion.
  useMkTarget = value ? humanName;
in
lib.optional (kind == "directory" && builtins.pathExists file) (
  if useMkTarget then
    {
      key = file;
      _file = file;
      imports = [ (import ./mk-target.nix value) ];
    }
  else
    file
```

<details><summary>Notes</summary>
<p>

Using `isAttrs` is redundant; `value ? humanName` is false for any non-attrs value.


We could also have whitelist or blacklist for attrnames,
e.g. this tries to identify a module:
```nix
isModule = builtins.isFunction value || builtins.any (attr: value ? ${attr}) [
  "key"
  "_file"
  "imports"
  "meta"
  "options"
  "config"
];
```

However it's not really needed and wouldn't work well with shorthand modules that don't use a `config` attr.

</p>
</details> 

My main concern with this design is that `/modules/<MODULE>/<PLATFORM>.nix` files may need access to (various) module args for arbitrary reasons. E.g. access to `pkgs` for setting defaults, `options` for writing warnings/assertions or checking specific options exist, `lib` for _many_ things, etc.

---

An alternative design would be to "inject" `mkTarget` into the module args. This involve `autoload.nix` applying the module args manually itself, using a similar impl to [`lib.modules.applyModuleArgs`](https://github.com/NixOS/nixpkgs/blob/786d8d6bc2ecad169a5d393784661ec43d02b247/lib/modules.nix#L634-L660):

```nix
path: kind:
let
  file = "${inputs.self}/modules/${path}/${for}.nix";
  value = import file;

  # Detect whether the file's value has an argument named `mkTarget`
  useMkTarget = builtins.isFunction value && (builtins.functionArgs value) ? mkTarget;

  # NOTE: `mkTarget` cannot be distributed normally through the module system
  # due to issues of infinite recursion.
  mkTarget = import ./mk-target.nix;
in
lib.optional (kind == "directory" && builtins.pathExists file) (
  if useMkTarget then
    { config, ... }@args:
    let
      # Based on `lib.modules.applyModuleArgs`
      context = name: ''while evaluating the module argument `${name}' in "${toString file}":'';
      extraArgs = lib.pipe value [
        builtins.functionArgs
        (args: builtins.removeAttrs args [ "mkTarget" ])
        (builtins.mapAttrs (name: _: addErrorContext (context name) (args.${name} or config._module.args.${name})))
      ];
      module = value (args // extraArgs // { inherit mkTarget; });
    in
    {
      key = file;
      _file = file;
      imports = [ module ];
    }
  else
    file
```

This would be used as:

```nix
# modules/foo/hm.nix
{ lib, config, mkTarget, ... }:
mkTarget {
  # ...
}
```
created_at: 2025-05-13T16:41:23Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,96 @@
+# See docs/src/modules.md#mkTarget for full explanation
+{
+  # Used when generating options; `stylix.targets.${name}`
+  name,
+
+  # Used when generating enable option; `stylix.targets.${name}.enable` has a
+  # description of "Whether to enable theming for ${humanName}."
+  humanName,
+
+  # Whether the target should be auto-enabled
+  autoEnable ? true,
+
+  # Attributes will be mapped to `stylix.targets.${name}.${option}`
+  extraOptions ? { },
+
+  # Function(s) should take the Stylix attributes that it needs (e.g. `colors`,
+  # `image`, `fonts`) and `cfg` if necessary. This `cfg` is an alias for
+  # `config.stylix.targets.${name}`. Function(s) should return an attribute set
+  # to be merged into `config`. If any of the Stylix attributes needed by a/the
+  # function are `null`, AKA unset, the configuration will not be merged.
+  configElements ? [ ],
+
+  # Same format as functions under `configElements`. Merged regardless of whether
+  # requested Stylix options are `null`. Avoid use when possible.
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args, while using this as their module body
+let
+  module =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+
+      # Get the function's de-structured arg names as a list
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+
+      # Get the Stylix attrs (option values) requested by the function's de-structured args
+      getStylixAttrs =
+        fn:
+        lib.genAttrs (functionArgNames fn) (
+          arg:
+          if arg == "cfg" then
+            cfg
+          else if arg == "colors" then
+            config.lib.stylix.colors
+          else
+            config.stylix.${arg}
+              or (throw "stylix: Stylix arg was requested by mk-target but does not exist: ${arg}")
+        );
+
+      # Call the config element function with the requested stylix attr values
+      # Returns a config definition
path: stylix/mk-target.nix
user: login: trueNAHO
body: Yes, but even for internal function documentations geared towards people hacking on `mkTarget`, it can be confusing when one function assumes too much external behavior.

However, I am also fine keeping the "Stylix arguments" detail:

```suggestion
      # Call the configuration function with its required Stylix arguments.
```

created_at: 2025-05-13T16:41:27Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,96 @@
+# See docs/src/modules.md#mkTarget for full explanation
+{
+  # Used when generating options; `stylix.targets.${name}`
+  name,
+
+  # Used when generating enable option; `stylix.targets.${name}.enable` has a
+  # description of "Whether to enable theming for ${humanName}."
+  humanName,
+
+  # Whether the target should be auto-enabled
+  autoEnable ? true,
+
+  # Attributes will be mapped to `stylix.targets.${name}.${option}`
+  extraOptions ? { },
+
+  # Function(s) should take the Stylix attributes that it needs (e.g. `colors`,
+  # `image`, `fonts`) and `cfg` if necessary. This `cfg` is an alias for
+  # `config.stylix.targets.${name}`. Function(s) should return an attribute set
+  # to be merged into `config`. If any of the Stylix attributes needed by a/the
+  # function are `null`, AKA unset, the configuration will not be merged.
+  configElements ? [ ],
+
+  # Same format as functions under `configElements`. Merged regardless of whether
+  # requested Stylix options are `null`. Avoid use when possible.
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args, while using this as their module body
path: stylix/mk-target.nix
user: login: MattSturgeon
body: Re https://github.com/danth/stylix/pull/1130#pullrequestreview-2837078061

> ```diff
> -# Note: we only return a plain module so that calling code can use module args, while using this as their module body
> ```

IMO this should stay all the while `/modules/<MODULE>/<PLATFORM>.nix` files are modules. This is a note to anyone working on `mkTarget` that the function should only return a plain attrs module body and not a full module function.

If we go with the other design, though, then I agree this can go, and in fact `mkTarget` can be simplified by actually having module args here, instead of in `let module = `.
created_at: 2025-05-13T16:43:31Z
author_association: MEMBER
diff_hunk: @@ -34,15 +34,41 @@ Other files needed by the module can also be stored within the
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`.
+Modules should be created using `mkTarget` whenever possible.
+
+When using `mkTarget`, request it _before_ the optional module args.
+See the below example.
+
+When `mkTarget` cannot be used, modules should declare an `enable` option using `mkEnableTarget`
+and guard their config definitions behind the appropriate `mkIf` conditions.
 This is similar to
 [`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
 from the standard library, however it integrates with
 [`stylix.enable`](./options/nixos.md#stylixenable) and
 [`stylix.autoEnable`](./options/nixos.md#stylixautoenable)
 and generates more specific documentation.
 
-A general format for modules is shown below.
+General formats for modules are shown below.
+
+Using `mkTarget`:
+
+```nix
+mkTarget:
+{ config, lib, ... }: # optional module args, after `mkTarget`
path: docs/src/modules.md
user: login: MattSturgeon
body: Re https://github.com/danth/stylix/pull/1130#pullrequestreview-2837078061

```diff
+#     mkTarget:
+#     { _config, ... }: # Optional module arguments.
```

This is a bad change, `_config` will cause the module system to panic when it applies module args because there is no `_config` arg in `_module.args` or `_module.specialArgs`.

Non-issue if we go with the other proposal to make `/modules/<MODULE>/<PLATFORM>.nix` files "args for mkTarget" instead of actual modules, since with that design we won't be allowing access to module args at all.
created_at: 2025-05-13T16:48:47Z
author_association: MEMBER
diff_hunk: @@ -8,7 +8,24 @@ builtins.concatLists (
     path: kind:
     let
       file = "${inputs.self}/modules/${path}/${for}.nix";
+      module = import file;
+      # Assume that a function-module whose first arg doesn't have any de-
+      # structured attrs is actually expecting `mkTarget` as its first arg;
+      #
+      # `mkTarget` cannot be distribtued normally through the module system
+      # due to issues of infinite recursion
+      expectsMkTarget =
+        builtins.isFunction module && builtins.functionArgs module == { };
path: stylix/autoload.nix
user: login: MattSturgeon
body: With the second approach, you would also be able to implement additional restrictions on `config` if desired, e.g.

```nix
config = args.config // {
  stylix = throw "Do not access `config.stylix` in a target module!";
};
```

I'd recommend leaving such restrictions for a future PR though so they can be designed/discussed/reviewed independently.
created_at: 2025-05-13T17:50:30Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,96 @@
+# See docs/src/modules.md#mkTarget for full explanation
+{
+  # Used when generating options; `stylix.targets.${name}`
+  name,
+
+  # Used when generating enable option; `stylix.targets.${name}.enable` has a
+  # description of "Whether to enable theming for ${humanName}."
+  humanName,
+
+  # Whether the target should be auto-enabled
+  autoEnable ? true,
+
+  # Attributes will be mapped to `stylix.targets.${name}.${option}`
+  extraOptions ? { },
+
+  # Function(s) should take the Stylix attributes that it needs (e.g. `colors`,
+  # `image`, `fonts`) and `cfg` if necessary. This `cfg` is an alias for
+  # `config.stylix.targets.${name}`. Function(s) should return an attribute set
+  # to be merged into `config`. If any of the Stylix attributes needed by a/the
+  # function are `null`, AKA unset, the configuration will not be merged.
+  configElements ? [ ],
+
+  # Same format as functions under `configElements`. Merged regardless of whether
+  # requested Stylix options are `null`. Avoid use when possible.
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args, while using this as their module body
+let
+  module =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+
+      # Get the function's de-structured arg names as a list
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+
+      # Get the Stylix attrs (option values) requested by the function's de-structured args
+      getStylixAttrs =
+        fn:
+        lib.genAttrs (functionArgNames fn) (
+          arg:
+          if arg == "cfg" then
+            cfg
path: stylix/mk-target.nix
user: login: Flameopathic
body: I don't quite understand what you mean; in its current state, `cfg = config.stylix.targets.${name}` is enforced whenever `cfg` is requested as an attribute to a `configElements` function, but I don't know how we could enforce this outside of that.
created_at: 2025-05-13T18:15:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,96 @@
+# See docs/src/modules.md#mkTarget for full explanation
+{
+  # Used when generating options; `stylix.targets.${name}`
+  name,
+
+  # Used when generating enable option; `stylix.targets.${name}.enable` has a
+  # description of "Whether to enable theming for ${humanName}."
+  humanName,
+
+  # Whether the target should be auto-enabled
+  autoEnable ? true,
+
+  # Attributes will be mapped to `stylix.targets.${name}.${option}`
+  extraOptions ? { },
+
+  # Function(s) should take the Stylix attributes that it needs (e.g. `colors`,
+  # `image`, `fonts`) and `cfg` if necessary. This `cfg` is an alias for
+  # `config.stylix.targets.${name}`. Function(s) should return an attribute set
+  # to be merged into `config`. If any of the Stylix attributes needed by a/the
+  # function are `null`, AKA unset, the configuration will not be merged.
+  configElements ? [ ],
+
+  # Same format as functions under `configElements`. Merged regardless of whether
+  # requested Stylix options are `null`. Avoid use when possible.
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args, while using this as their module body
+let
+  module =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+
+      # Get the function's de-structured arg names as a list
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+
+      # Get the Stylix attrs (option values) requested by the function's de-structured args
+      getStylixAttrs =
+        fn:
+        lib.genAttrs (functionArgNames fn) (
+          arg:
+          if arg == "cfg" then
+            cfg
path: stylix/mk-target.nix
user: login: trueNAHO
body: Oh yes, I somehow totally misunderstood this code just now. My bad.
created_at: 2025-05-13T19:03:45Z
author_association: MEMBER
diff_hunk: @@ -8,21 +8,37 @@ builtins.concatLists (
     path: kind:
     let
       file = "${inputs.self}/modules/${path}/${for}.nix";
-      module = import file;
-      # Assume that a function-module whose first arg doesn't have any de-
-      # structured attrs is actually expecting `mkTarget` as its first arg;
-      #
-      # `mkTarget` cannot be distribtued normally through the module system
-      # due to issues of infinite recursion
-      expectsMkTarget =
-        builtins.isFunction module && builtins.functionArgs module == { };
+      value = import file;
+
+      # Detect whether the file's value has an argument named `mkTarget`
+      useMkTarget =
+        builtins.isFunction value && (builtins.functionArgs value) ? mkTarget;
+
+      # NOTE: `mkTarget` cannot be distributed normally through the module system
+      # due to issues of infinite recursion.
+      mkTarget = import ./mk-target.nix;
     in
     lib.optional (kind == "directory" && builtins.pathExists file) (
-      if expectsMkTarget then
+      if useMkTarget then
+        { config, ... }@args:
+        let
+          # Based on `lib.modules.applyModuleArgs`
+          context =
+            name: ''while evaluating the module argument `${name}' in "${toString file}":'';
+          extraArgs = lib.pipe value [
+            builtins.functionArgs
+            (args: builtins.removeAttrs args [ "mkTarget" ])
path: stylix/autoload.nix
user: login: MattSturgeon
body: FYI this can also be written as:

```suggestion
            (lib.flip builtins.removeAttrs [ "mkTarget" ])
```

...if preferred.
created_at: 2025-05-13T19:59:51Z
author_association: MEMBER
diff_hunk: @@ -8,7 +8,24 @@ builtins.concatLists (
     path: kind:
     let
       file = "${inputs.self}/modules/${path}/${for}.nix";
+      module = import file;
+      # Assume that a function-module whose first arg doesn't have any de-
+      # structured attrs is actually expecting `mkTarget` as its first arg;
+      #
+      # `mkTarget` cannot be distribtued normally through the module system
+      # due to issues of infinite recursion
+      expectsMkTarget =
+        builtins.isFunction module && builtins.functionArgs module == { };
path: stylix/autoload.nix
user: login: trueNAHO
body: > Following up on @trueNAHO's idea of `/modules/<MODULE>/<PLATFORM>.nix` files being "args for mkTarget" instead of actual modules:
>
> > Ideally, in the future, this function returns an actual module by better integrating with the `/stylix/autoload.nix` logic, allowing the following target simplification and preventing access to unguarded module arguments by requiring `/modules/<MODULE>/<PLATFORM>.nix` files to be attribute sets instead of modules:
>
> While it's a big shift for the design of the PR, I do like this more. Especially if mkTarget-files can reliably indicate that they are not modules, e.g. via the presence of some attr like `humanName`.
>
> [...]
>
> My main concern with this design is that `/modules/<MODULE>/<PLATFORM>.nix` files may need access to (various) module args for arbitrary reasons. E.g. access to `pkgs` for setting defaults, `options` for writing warnings/assertions or checking specific options exist, `lib` for _many_ things, etc.

It would be great if these non-Stylix arguments can be seemlessly requested by function modules as:

```nix
{
  # Stylix arguments.
  colors, fonts

  # Non-Stylix arguments, representing arbitrary module arguments.
  config, inputs, lib, pkgs,

  # Convenience argument.
  cfg
}: /* ... */
```

That way, `/modules/<MODULE>/<PLATFORM>.nix` files contain only an attribute set without explicitly calling `mkTarget` themselves:

> ```nix
> #     {
> #       name = "example";
> #       humanName = "Example Target";
> #
> #       generalConfig =
> #         { lib, pkgs }:
> #         lib.mkIf complexCondition {
> #           home.packages = [ pkgs.hello ];
> #         };
> #
> #       configElements = [
> #         { programs.example.theme.name = "stylix"; }
> #
> #         (
> #           { colors }:
> #           {
> #             programs.example.theme.background = colors.base00;
> #           }
> #         )
> #
> #         (
> #           { fonts }:
> #           {
> #             programs.example.font.name = fonts.monospace.name;
> #           }
> #         )
> #       ];
> #     }
> ```

The `/stylix/autoload.nix` logic detects `/modules/<MODULE>/<PLATFORM>.nix` targets expecting to be `mkTarget` instances with `builtins.isAttrs` and calls them with something like:

```nix
mkTarget (import "/modules/<MODULE>/<PLATFORM>.nix")
```

The `mkTarget` detection can be further restricted by checking for required `mkTarget` arguments:

```diff
-builtins.isAttrs module
+builtins.isAttrs module && module ? name && module ? humanName
```

Currently, the `/stylix/mk-target.nix` file header mentions the following use case:

> ```
> #       generalConfig =
> #         { lib, pkgs }:
> #         lib.mkIf complexCondition {
> #           home.packages = [ pkgs.hello ];
> #         };
> ```

I just realized this may not be supported yet before migrating to this new design.

created_at: 2025-05-13T20:00:41Z
author_association: MEMBER
diff_hunk: @@ -8,21 +8,37 @@ builtins.concatLists (
     path: kind:
     let
       file = "${inputs.self}/modules/${path}/${for}.nix";
-      module = import file;
-      # Assume that a function-module whose first arg doesn't have any de-
-      # structured attrs is actually expecting `mkTarget` as its first arg;
-      #
-      # `mkTarget` cannot be distribtued normally through the module system
-      # due to issues of infinite recursion
-      expectsMkTarget =
-        builtins.isFunction module && builtins.functionArgs module == { };
+      value = import file;
+
+      # Detect whether the file's value has an argument named `mkTarget`
+      useMkTarget =
+        builtins.isFunction value && (builtins.functionArgs value) ? mkTarget;
+
+      # NOTE: `mkTarget` cannot be distributed normally through the module system
+      # due to issues of infinite recursion.
+      mkTarget = import ./mk-target.nix;
     in
     lib.optional (kind == "directory" && builtins.pathExists file) (
-      if expectsMkTarget then
+      if useMkTarget then
+        { config, ... }@args:
+        let
+          # Based on `lib.modules.applyModuleArgs`
+          context =
+            name: ''while evaluating the module argument `${name}' in "${toString file}":'';
+          extraArgs = lib.pipe value [
+            builtins.functionArgs
+            (args: builtins.removeAttrs args [ "mkTarget" ])
+            (builtins.mapAttrs (
+              name: _:
+              lib.addErrorContext (context name) (args.${name} or config._module.args.${name})
path: stylix/autoload.nix
user: login: MattSturgeon
body: ```suggestion
              builtins.addErrorContext (context name) (args.${name} or config._module.args.${name})
```

Can be accessed via `builtins` if preferred
created_at: 2025-05-13T20:00:58Z
author_association: MEMBER
diff_hunk: @@ -8,21 +8,37 @@ builtins.concatLists (
     path: kind:
     let
       file = "${inputs.self}/modules/${path}/${for}.nix";
-      module = import file;
-      # Assume that a function-module whose first arg doesn't have any de-
-      # structured attrs is actually expecting `mkTarget` as its first arg;
-      #
-      # `mkTarget` cannot be distribtued normally through the module system
-      # due to issues of infinite recursion
-      expectsMkTarget =
-        builtins.isFunction module && builtins.functionArgs module == { };
+      value = import file;
+
+      # Detect whether the file's value has an argument named `mkTarget`
+      useMkTarget =
+        builtins.isFunction value && (builtins.functionArgs value) ? mkTarget;
+
+      # NOTE: `mkTarget` cannot be distributed normally through the module system
+      # due to issues of infinite recursion.
+      mkTarget = import ./mk-target.nix;
     in
     lib.optional (kind == "directory" && builtins.pathExists file) (
-      if expectsMkTarget then
+      if useMkTarget then
+        { config, ... }@args:
+        let
+          # Based on `lib.modules.applyModuleArgs`
path: stylix/autoload.nix
user: login: MattSturgeon
body: ```suggestion
          # Based on `lib.modules.applyModuleArgs`
          #
          # Apply `mkTarget` as a special arg without actually using `specialArgs`,
          # which cannot be defined from within a configuration.
```

Maybe we should be more explicit about _why_ we do this?
created_at: 2025-05-13T20:04:00Z
author_association: MEMBER
diff_hunk: @@ -8,7 +8,24 @@ builtins.concatLists (
     path: kind:
     let
       file = "${inputs.self}/modules/${path}/${for}.nix";
+      module = import file;
+      # Assume that a function-module whose first arg doesn't have any de-
+      # structured attrs is actually expecting `mkTarget` as its first arg;
+      #
+      # `mkTarget` cannot be distribtued normally through the module system
+      # due to issues of infinite recursion
+      expectsMkTarget =
+        builtins.isFunction module && builtins.functionArgs module == { };
path: stylix/autoload.nix
user: login: MattSturgeon
body: If I'm understanding correctly, the issue with your proposal is that you can only access args when defining config (via `configElements` or `generalConfig`).

That doesn't help if options need to be declared, additional modules (renames, etc) imported, or some shared common logic implemented in a top-level `let in` block.

Having module args that are scoped at the _top level_ of the function is important for these cases.

You _could_ have a "mkTarget module" concept, that like normal modules can be either `attrs` or `attrs -> attrs`. The issue here is distinguishing the function from a normal module.

Maybe that could be done by having `mkTarget` modules _always_ need a particular named input?

Or alternatively you could assume _all_ /modules/<MODULE>/<PLATFORM>.nix files are always `mkTarget` modules instead of normal modules, unless some specific indicator is added?

E.g. using a filename:

```nix
path: kind:
let
  basePath = "${inputs.self}/modules/${path}";
  mkTargetName = basePath + "/${for}.nix";
  moduleName = basePath + "/${for}-module.nix";
  mkTargetFile = basePath + mkTargetName;
  moduleFile = basePath + moduleName;
  hasMkTargetFile = builtins.pathExists mkTargetFile;
  hasModule = builtins.pathExists moduleFile;
in
lib.optional (kind == "directory") (
  assert !(hasMkTargetFile && hasModule);
  if  hasMkTargetFile then
    {
      key = mkTargetFile;
      _file = mkTargetFile;
      imports = [ (import ./mk-target.nix (import mkTargetFile)) ];
    }
  else if hasModule then
    moduleFile
  else
    throw "Neither `${mkTargetName}` nor `${moduleName}` exist in `${basePath}`."
)
```
created_at: 2025-05-13T20:18:52Z
author_association: MEMBER
diff_hunk: @@ -8,7 +8,24 @@ builtins.concatLists (
     path: kind:
     let
       file = "${inputs.self}/modules/${path}/${for}.nix";
+      module = import file;
+      # Assume that a function-module whose first arg doesn't have any de-
+      # structured attrs is actually expecting `mkTarget` as its first arg;
+      #
+      # `mkTarget` cannot be distribtued normally through the module system
+      # due to issues of infinite recursion
+      expectsMkTarget =
+        builtins.isFunction module && builtins.functionArgs module == { };
path: stylix/autoload.nix
user: login: trueNAHO
body: > If I'm understanding correctly, the issue with your proposal is that you can only access args when defining config (via `configElements` or `generalConfig`).
>
> That doesn't help if options need to be declared, additional modules (renames, etc) imported, or some shared common logic implemented in a top-level `let in` block.
>
> Having module args that are scoped at the _top level_ of the function is important for these cases.

What about requesting arguments in `extraOptions`:

```diff
   # Additional options to be added in the stylix.targets.${name} namespace along
   # the stylix.targets.${name}.enable option.
   #
   # For example, an extension guard used in the configuration can be declared as
   # follows:
   #
-  #     { extension.enable = lib.mkEnableOption "the bloated dependency"; }
-  extraOptions ? { },
+  #     { lib }: {
+  #       extension.enable = lib.mkEnableOption "the bloated dependency";
+  #     }
+  extraOptions ? (_: { }),
```

Preventing access to unguarded module arguments inheritently reduces the scope of accidentally accessing unguarded arguments, seems like a very valuable restriction to add.
created_at: 2025-05-13T20:30:34Z
author_association: MEMBER
diff_hunk: @@ -8,7 +8,24 @@ builtins.concatLists (
     path: kind:
     let
       file = "${inputs.self}/modules/${path}/${for}.nix";
+      module = import file;
+      # Assume that a function-module whose first arg doesn't have any de-
+      # structured attrs is actually expecting `mkTarget` as its first arg;
+      #
+      # `mkTarget` cannot be distribtued normally through the module system
+      # due to issues of infinite recursion
+      expectsMkTarget =
+        builtins.isFunction module && builtins.functionArgs module == { };
path: stylix/autoload.nix
user: login: MattSturgeon
body: > Preventing access to unguarded module arguments inheritently reduces the scope of accidentally accessing unguarded arguments, seems like a very valuable restriction to add.

This mostly applies to `config`, not other module args.

Something like https://github.com/danth/stylix/pull/1130#discussion_r2087357257 would resolve that.

> > That doesn't help if options need to be declared, additional modules (renames, etc) imported, or some shared common logic implemented in a top-level `let in` block.
> > Having module args that are scoped at the _top level_ of the function is important for these cases.
> 
> What about requesting arguments in `extraOptions`:

That'd _help_ but it's not really addressing the issue; _other_ module args, common logic scoped _outside_ of `configElements`, `generalConfig`, `extraOptions`, etc.

---

https://github.com/danth/stylix/pull/1130#discussion_r2087566335 or the current approach could support calling `mkTarget` with a "function or attrs" value, and if it is a function pass it some commonly useful args such as `lib`, a restricted `config`, `options`, anything present in `_module.args` or `_module.specialArgs`, etc?

```nix
# mk-target-module.nix
# A module that normalises the `fnOrAttrs` input to attrs that can be applied to `mkTarget`
fnOrAttrs:
{ config, ... }@args:
let
  call = throw "TODO ...";
  attrs = if builtins.isFunction fnOrAttrs then call fnOrAttrs else fnOrAttrs;
in
{
  imports = [ (import ./mk-target.nix attrs) ];
}
```
created_at: 2025-05-13T20:39:47Z
author_association: MEMBER
diff_hunk: @@ -1,59 +1,52 @@
-{ config, lib, ... }:
-
 {
-  options.stylix.targets.hyprland = {
-    enable = config.lib.stylix.mkEnableTarget "Hyprland" true;
-    hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
-      config.stylix.image != null
-    );
-  };
-
-  config =
-    let
-      cfg = config.stylix.targets.hyprland;
-    in
-    lib.mkIf
-      (
-        config.stylix.enable
-        && cfg.enable
-        && config.wayland.windowManager.hyprland.enable
-      )
-      (
-        lib.mkMerge [
+  config,
+  lib,
+  mkTarget,
+  ...
+}:
+mkTarget {
+  name = "hyprland";
+  humanName = "Hyprland";
+  extraOptions.hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
+    config.stylix.image != null
+  );
+  configElements = [
+    (
+      { colors }:
+      {
+        wayland.windowManager.hyprland.settings =
+          let
+            rgb = color: "rgb(${color})";
+            rgba = color: alpha: "rgba(${color}${alpha})";
path: modules/hyprland/hm.nix
user: login: Eveeifyeve
body: I feel like there should be a function made like `mkRGB` and `mkRGBA` similar to what I am doing for hexadecimal colors https://github.com/danth/stylix/pull/1274 
created_at: 2025-05-15T13:59:02Z
author_association: MEMBER
diff_hunk: @@ -1,59 +1,52 @@
-{ config, lib, ... }:
-
 {
-  options.stylix.targets.hyprland = {
-    enable = config.lib.stylix.mkEnableTarget "Hyprland" true;
-    hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
-      config.stylix.image != null
-    );
-  };
-
-  config =
-    let
-      cfg = config.stylix.targets.hyprland;
-    in
-    lib.mkIf
-      (
-        config.stylix.enable
-        && cfg.enable
-        && config.wayland.windowManager.hyprland.enable
-      )
-      (
-        lib.mkMerge [
+  config,
+  lib,
+  mkTarget,
+  ...
+}:
+mkTarget {
+  name = "hyprland";
+  humanName = "Hyprland";
+  extraOptions.hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" (
+    config.stylix.image != null
+  );
+  configElements = [
+    (
+      { colors }:
+      {
+        wayland.windowManager.hyprland.settings =
+          let
+            rgb = color: "rgb(${color})";
+            rgba = color: alpha: "rgba(${color}${alpha})";
path: modules/hyprland/hm.nix
user: login: MattSturgeon
body: Definitely not in scope for this PR. Maybe open a dedicated issue or PR, or incorporate it into #1274?
created_at: 2025-05-15T14:02:55Z
author_association: MEMBER
diff_hunk: @@ -34,33 +34,43 @@ folder, using any name which is not on the list above.
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`. This is
-similar to
-[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
-from the standard library, however it integrates with
-[`stylix.enable`](./options/nixos.md#stylixenable) and
-[`stylix.autoEnable`](./options/nixos.md#stylixautoenable) and generates more
-specific documentation.
+Modules should be created using the `mkTarget` function whenever possible (see
+the `/stylix/mk-target.nix` documentation for more details):
 
-A general format for modules is shown below.
+```nix
+{ config, lib, mkTarget ... }:
+mkTarget {
+  name = "example";
+  humanName = "Example Target";
+
+  configElements =
+    { colors }:
+    {
+      programs.example.theme.background = colors.base00;
+    };
+}
+```
+
path: docs/src/modules.md
user: login: MattSturgeon
body: This is kinda an implementation detail, but it feels like it's work highlighting as it could trip someone up?

```suggestion

> [!IMPORTANT]
> The `mkTarget` argument is only available to modules imported by Stylix's [autoload system](https://github.com/danth/stylix/blob/-/stylix/autoload.nix),
> e.g., `modules/«target»/«platform».nix` modules.
>
> I.e., it is not available to normal modules imported via the `imports` list.

```


created_at: 2025-05-16T01:06:36Z
author_association: MEMBER
diff_hunk: @@ -34,33 +34,43 @@ folder, using any name which is not on the list above.
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`. This is
-similar to
-[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
-from the standard library, however it integrates with
-[`stylix.enable`](./options/nixos.md#stylixenable) and
-[`stylix.autoEnable`](./options/nixos.md#stylixautoenable) and generates more
-specific documentation.
+Modules should be created using the `mkTarget` function whenever possible (see
+the `/stylix/mk-target.nix` documentation for more details):
 
-A general format for modules is shown below.
+```nix
+{ config, lib, mkTarget ... }:
+mkTarget {
+  name = "example";
+  humanName = "Example Target";
+
+  configElements =
+    { colors }:
+    {
+      programs.example.theme.background = colors.base00;
+    };
+}
+```
+
+When the `mkTarget` function cannot be used, modules must manually replicate its
+safeguarding functionality:
path: docs/src/modules.md
user: login: MattSturgeon
body: ```suggestion
safeguarding checks:
```

```suggestion
safeguarding conditions:
```

```suggestion
safeguarding behaviour:
```
created_at: 2025-05-16T01:09:51Z
author_association: MEMBER
diff_hunk: @@ -34,33 +34,43 @@ folder, using any name which is not on the list above.
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`. This is
-similar to
-[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
-from the standard library, however it integrates with
-[`stylix.enable`](./options/nixos.md#stylixenable) and
-[`stylix.autoEnable`](./options/nixos.md#stylixautoenable) and generates more
-specific documentation.
+Modules should be created using the `mkTarget` function whenever possible (see
+the `/stylix/mk-target.nix` documentation for more details):
 
-A general format for modules is shown below.
+```nix
+{ config, lib, mkTarget ... }:
+mkTarget {
+  name = "example";
+  humanName = "Example Target";
+
+  configElements =
+    { colors }:
+    {
+      programs.example.theme.background = colors.base00;
+    };
+}
+```
+
+When the `mkTarget` function cannot be used, modules must manually replicate its
+safeguarding functionality:
 
 ```nix
 { config, lib, ... }:
 {
-  options.stylix.targets.«name».enable =
-    config.lib.stylix.mkEnableTarget "«human readable name»" true;
+  options.stylix.targets.example.enable =
+    config.lib.stylix.mkEnableTarget "Example Target" true;
 
   config =
-    lib.mkIf (config.stylix.enable && config.stylix.targets.«name».enable)
+    lib.mkIf (config.stylix.enable && config.stylix.targets.example.enable)
       {
-        programs.«name».backgroundColor = config.lib.stylix.colors.base00;
+        programs.example.theme.background = config.lib.stylix.colors.base00;
       };
 }
path: docs/src/modules.md
user: login: MattSturgeon
body: IMO it's clearer with the «unicode» arrows indicating placeholder values, I don't see any reason to change this example in this PR...

```suggestion
  options.stylix.targets.«name».enable =
    config.lib.stylix.mkEnableTarget "«human readable name»" true;

  config =
    lib.mkIf (config.stylix.enable && config.stylix.targets.«name».enable)
      {
        programs.«name».backgroundColor = config.lib.stylix.colors.base00;
      };
}
```

If anything, maybe the `mkTarget` example above should use the same placeholder pattern?
created_at: 2025-05-16T01:14:35Z
author_association: MEMBER
diff_hunk: @@ -8,7 +8,45 @@ builtins.concatLists (
     path: kind:
     let
       file = "${inputs.self}/modules/${path}/${for}.nix";
+      value = import file;
path: stylix/autoload.nix
user: login: MattSturgeon
body: ```suggestion
      fn = import file;
```

Technically, we don't know it's a function yet when assigning this binding, however in all the places we _use_ `value`, we _have_ checked it is a function.

So maybe this is a better name?

Could also do `maybeFn`, but that could get wordy.

Or `module`, but that could conflict with L41.

Although, L41 could be inlined into the `imports` list: 
```nix
imports = [
  (value (args // extraArgs // { inherit mkTarget; }))
];
```

_Personally_ I think something like `fn` or `moduleFn` is best, but as with all naming it's very subjective!
created_at: 2025-05-16T01:22:50Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,161 @@
+# Provides a consistent target interface, minimizing boilerplate and
path: stylix/mk-target.nix
user: login: MattSturgeon
body: If we're going to document it here, let's use an [RFC145](https://github.com/NixOS/rfcs/blob/master/rfcs/0145-doc-strings.md) doc-comment.

This will (usually) show up in the repl when using `:doc mkTarget`, will be supported by tools like [`nixdoc`](https://github.com/nix-community/nixdoc) if Stylix ever add auto-generated function docs (e.g. [wip nixvim docs](https://github.com/nix-community/nixvim/pull/3049), [wip `treefmt.withConfig` docs](https://github.com/NixOS/nixpkgs/compare/31eef1e9520b0c4468362eca98296d040c9679e3...MattSturgeon:nixpkgs:treefmt/referenceDocs)), and is easier to read, as there's no leading `#` prefixing each line:

````nix
/**
  Provides a consistent target interface, minimizing boilerplate and
  (etc...)

  # Type

  ```
  mkTarget :: AttrSet -> ModuleBody
  ```

  # Examples

  This function returns the module body, not an actual module.

  ```nix
  { lib, mkTarget, ... }:
  # ...
  ```

  (etc...)

  # Inputs

  `config`
  : `name`
    : descr for name

  : `humanName`
    : description for human name

  (etc...)

  # Outputs

  A nix Module body, using the "plain attrs" syntax.
  This form should be used directly after your module args.
*/
````
created_at: 2025-05-16T01:38:15Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,161 @@
+# Provides a consistent target interface, minimizing boilerplate and
+# automatically safeguarding declarations related to disabled options.
+#
+# This function returns the module body, not an actual module.
+#
+# The /modules/<MODULE>/<PLATFORM>.nix modules should use this function as
+# follows:
+#
+#     { mkTarget, lib... }:
+#     mkTarget {
+#       name = "example";
+#       humanName = "Example Target";
+#
+#       generalConfig =
+#         lib.mkIf complexCondition {
+#           home.packages = [ pkgs.hello ];
+#         };
+#
+#       configElements = [
+#         { programs.example.theme.name = "stylix"; }
+#
+#         (
+#           { colors }:
+#           {
+#             programs.example.theme.background = colors.base00;
+#           }
+#         )
+#
+#         (
+#           { fonts }:
+#           {
+#             programs.example.font.name = fonts.monospace.name;
+#           }
+#         )
+#       ];
+#     }
+#
+# Note that `mkTarget` is provided in the same place as module arguments.
+#
+# Ideally, in the future, this function also prevents unguarded module arguments
+# by limiting the arguments passed through `/stylix/autoload.nix`
path: stylix/mk-target.nix
user: login: MattSturgeon
body: Is this comment more appropriate in autoload.nix? Since that is where the restriction will be added, not here.
created_at: 2025-05-16T01:39:39Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,161 @@
+# Provides a consistent target interface, minimizing boilerplate and
+# automatically safeguarding declarations related to disabled options.
+#
+# This function returns the module body, not an actual module.
+#
+# The /modules/<MODULE>/<PLATFORM>.nix modules should use this function as
path: stylix/mk-target.nix
user: login: MattSturgeon
body: Unicode chevrons for placeholders:

```suggestion
# The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
```
created_at: 2025-05-16T01:41:36Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,161 @@
+# Provides a consistent target interface, minimizing boilerplate and
+# automatically safeguarding declarations related to disabled options.
+#
+# This function returns the module body, not an actual module.
+#
+# The /modules/<MODULE>/<PLATFORM>.nix modules should use this function as
+# follows:
+#
+#     { mkTarget, lib... }:
+#     mkTarget {
+#       name = "example";
+#       humanName = "Example Target";
+#
+#       generalConfig =
+#         lib.mkIf complexCondition {
+#           home.packages = [ pkgs.hello ];
+#         };
+#
+#       configElements = [
+#         { programs.example.theme.name = "stylix"; }
+#
+#         (
+#           { colors }:
+#           {
+#             programs.example.theme.background = colors.base00;
+#           }
+#         )
+#
+#         (
+#           { fonts }:
+#           {
+#             programs.example.font.name = fonts.monospace.name;
+#           }
+#         )
+#       ];
+#     }
+#
+# Note that `mkTarget` is provided in the same place as module arguments.
+#
+# Ideally, in the future, this function also prevents unguarded module arguments
+# by limiting the arguments passed through `/stylix/autoload.nix`
+{
+  # The target name used to generate options in the stylix.targets.${name}
+  # namespace.
+  name,
+
+  # The descriptive target name passed to the lib.mkEnableOption function when
+  # generating the stylix.targets.${name}.enable option.
+  humanName,
+
+  # Whether the target should be automatically enabled by default according to
+  # the stylix.autoEnable option.
+  #
+  # This should be disabled if manual setup is required or if auto-enabling
+  # causes issues.
+  autoEnable ? true,
+
+  # Additional options to be added in the stylix.targets.${name} namespace along
+  # the stylix.targets.${name}.enable option.
+  #
+  # For example, an extension guard used in the configuration can be declared as
+  # follows:
+  #
+  #     { extension.enable = lib.mkEnableOption "the bloated dependency"; }
+  extraOptions ? { },
+
+  # Configuration functions that are automatically safeguarded when any of their
+  # arguments is disabled. The provided 'cfg' argument conveniently aliases to
+  # config.stylix.targets.${name}.
+  #
+  # For example, the following configuration is not merged if the stylix.colors
+  # option is disabled:
+  #
+  #     (
+  #       { colors }:
+  #       {
+  #         programs.example.theme.background = colors.base00;
+  #       }
+  #     )
+  #
+  # The 'cfg' alias can be accessed as follows:
+  #
+  #     (
+  #       { cfg }:
+  #       {
+  #         programs.example.extension.enable = cfg.extension.enable;
+  #       }
+  #     )
+  configElements ? [ ],
+
+  # This argument mirrors the configElements argument but intentionally lacks
+  # automatic safeguarding and should only be used for complex configurations
+  # where configElements is unsuitable.
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args,
+# while using this as their module body
+let
+  module =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+
+      # Get the list of function de-structured argument names.
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+
+      getStylixAttrs =
+        fn:
+        lib.genAttrs (functionArgNames fn) (
+          arg:
+          if arg == "cfg" then
+            cfg
+          else if arg == "colors" then
+            config.lib.stylix.colors
+          else
+            config.stylix.${arg}
+              or (throw "stylix: Stylix argument requested by mkTarget does not exist: ${arg}")
path: stylix/mk-target.nix
user: login: MattSturgeon
body: Would this flow better with the name inline?

```suggestion
              or (throw "stylix: Stylix argument `${arg}` requested by mkTarget does not exist")
```

---

Is it worth printing the available names?

```suggestion
              or (throw "stylix: Stylix argument `${arg}` requested by mkTarget does not exist. Expected one of `cfg`, `colors`, ${
                lib.concatMapStringsSep ", " (name: "`${name}`") (builtins.attrNames config.stylix)
              }")
```

If you'd rather quote the names instead of backticks, you can use `builtins.toJSON` to render a quoted string.
created_at: 2025-05-16T01:52:41Z
author_association: MEMBER
diff_hunk: @@ -34,33 +34,43 @@ folder, using any name which is not on the list above.
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`. This is
-similar to
-[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
-from the standard library, however it integrates with
-[`stylix.enable`](./options/nixos.md#stylixenable) and
-[`stylix.autoEnable`](./options/nixos.md#stylixautoenable) and generates more
-specific documentation.
+Modules should be created using the `mkTarget` function whenever possible (see
+the `/stylix/mk-target.nix` documentation for more details):
path: docs/src/modules.md
user: login: MattSturgeon
body: ```suggestion
the in-source documentation for more details: [`/stylix/mk-target.nix`](https://github.com/danth/stylix/blob/-/stylix/mk-target.nix)):
```
created_at: 2025-05-16T01:55:22Z
author_association: MEMBER
diff_hunk: @@ -34,33 +34,43 @@ folder, using any name which is not on the list above.
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`. This is
-similar to
-[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
-from the standard library, however it integrates with
-[`stylix.enable`](./options/nixos.md#stylixenable) and
-[`stylix.autoEnable`](./options/nixos.md#stylixautoenable) and generates more
-specific documentation.
+Modules should be created using the `mkTarget` function whenever possible (see
+the `/stylix/mk-target.nix` documentation for more details):
 
-A general format for modules is shown below.
+```nix
+{ config, lib, mkTarget ... }:
+mkTarget {
+  name = "example";
+  humanName = "Example Target";
+
+  configElements =
+    { colors }:
+    {
+      programs.example.theme.background = colors.base00;
+    };
+}
+```
+
+When the `mkTarget` function cannot be used, modules must manually replicate its
+safeguarding functionality:
path: docs/src/modules.md
user: login: trueNAHO
body: Yes, "functionality" is somewhat misguiding.

> ```suggestion
> safeguarding behaviour:
> ```

I prefer this one.
created_at: 2025-05-16T16:32:46Z
author_association: MEMBER
diff_hunk: @@ -34,33 +34,43 @@ folder, using any name which is not on the list above.
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`. This is
-similar to
-[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
-from the standard library, however it integrates with
-[`stylix.enable`](./options/nixos.md#stylixenable) and
-[`stylix.autoEnable`](./options/nixos.md#stylixautoenable) and generates more
-specific documentation.
+Modules should be created using the `mkTarget` function whenever possible (see
+the `/stylix/mk-target.nix` documentation for more details):
path: docs/src/modules.md
user: login: trueNAHO
body: > ```suggestion
> the in-source documentation for more details: [`/stylix/mk-target.nix`](https://github.com/danth/stylix/blob/-/stylix/mk-target.nix)):
> ```

I think the `:` indication inside `()` is overkill and too formal. But the "in-source" specification and the convenience URL are good:

```suggestion
the [`/stylix/mk-target.nix`](
https://github.com/danth/stylix/blob/-/stylix/mk-target.nix) in-source
documentation for more details):
```

created_at: 2025-05-16T16:38:12Z
author_association: MEMBER
diff_hunk: @@ -34,33 +34,43 @@ folder, using any name which is not on the list above.
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`. This is
-similar to
-[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
-from the standard library, however it integrates with
-[`stylix.enable`](./options/nixos.md#stylixenable) and
-[`stylix.autoEnable`](./options/nixos.md#stylixautoenable) and generates more
-specific documentation.
+Modules should be created using the `mkTarget` function whenever possible (see
+the `/stylix/mk-target.nix` documentation for more details):
 
-A general format for modules is shown below.
+```nix
+{ config, lib, mkTarget ... }:
+mkTarget {
+  name = "example";
+  humanName = "Example Target";
+
+  configElements =
+    { colors }:
+    {
+      programs.example.theme.background = colors.base00;
+    };
+}
+```
+
path: docs/src/modules.md
user: login: trueNAHO
body: > This is kinda an implementation detail, but it feels like it's work highlighting as it could trip someone up?

This implementation detail should never be relevant when working on the `/modules/<MODULE>/<PLATFORM>.nix` modules. Even our most complex `/modules/firefox/hm.nix` module would only need to know that it needs to do something along the lines of:

```nix
{ mkTarget, ... }:
map mkTarget targets
```

Anyone benefiting from this information is working on the core system and is very likely already aware that we are providing `mkTarget` and that it is not a Nix primitive.

I am in favor of not adding this.

created_at: 2025-05-16T16:47:05Z
author_association: MEMBER
diff_hunk: @@ -34,33 +34,43 @@ folder, using any name which is not on the list above.
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`. This is
-similar to
-[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
-from the standard library, however it integrates with
-[`stylix.enable`](./options/nixos.md#stylixenable) and
-[`stylix.autoEnable`](./options/nixos.md#stylixautoenable) and generates more
-specific documentation.
+Modules should be created using the `mkTarget` function whenever possible (see
+the `/stylix/mk-target.nix` documentation for more details):
 
-A general format for modules is shown below.
+```nix
+{ config, lib, mkTarget ... }:
+mkTarget {
+  name = "example";
+  humanName = "Example Target";
+
+  configElements =
+    { colors }:
+    {
+      programs.example.theme.background = colors.base00;
+    };
+}
+```
+
+When the `mkTarget` function cannot be used, modules must manually replicate its
+safeguarding functionality:
 
 ```nix
 { config, lib, ... }:
 {
-  options.stylix.targets.«name».enable =
-    config.lib.stylix.mkEnableTarget "«human readable name»" true;
+  options.stylix.targets.example.enable =
+    config.lib.stylix.mkEnableTarget "Example Target" true;
 
   config =
-    lib.mkIf (config.stylix.enable && config.stylix.targets.«name».enable)
+    lib.mkIf (config.stylix.enable && config.stylix.targets.example.enable)
       {
-        programs.«name».backgroundColor = config.lib.stylix.colors.base00;
+        programs.example.theme.background = config.lib.stylix.colors.base00;
       };
 }
path: docs/src/modules.md
user: login: trueNAHO
body: > IMO it's clearer with the «unicode» arrows indicating placeholder values, I don't see any reason to change this example in this PR...

I slightly changed the example to make it more realistic. However, I agree that this change is probably out of the scope of this PR.

> If anything, maybe the `mkTarget` example above should use the same placeholder pattern?

Yes, that would make more sense in that case.

created_at: 2025-05-16T16:53:21Z
author_association: MEMBER
diff_hunk: @@ -8,7 +8,45 @@ builtins.concatLists (
     path: kind:
     let
       file = "${inputs.self}/modules/${path}/${for}.nix";
+      value = import file;
path: stylix/autoload.nix
user: login: trueNAHO
body: > Technically, we don't know it's a function yet when assigning this binding, however in all the places we _use_ `value`, we _have_ checked it is a function.
>
> So maybe this is a better name?
>
> Could also do `maybeFn`, but that could get wordy.
>
> Or `module`, but that could conflict with L41.
>
> Although, L41 could be inlined into the `imports` list:
>
> ```nix
> imports = [
>   (value (args // extraArgs // { inherit mkTarget; }))
> ];
> ```

Although I am biased because I generally [prefer inlining code](http://number-none.com/blow/blog/programming/2014/09/26/carmack-on-inlined-code.html), I am fine with the coding style of self documenting the code with intermediate variables.

Since the `module` identifier looks very appropriate, I prefer inlining the current `module` expression and using `module` as:

```suggestion
      module = import file;
```

created_at: 2025-05-16T17:00:59Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,161 @@
+# Provides a consistent target interface, minimizing boilerplate and
path: stylix/mk-target.nix
user: login: trueNAHO
body: > If we're going to document it here, let's use an [RFC145](https://github.com/NixOS/rfcs/blob/master/rfcs/0145-doc-strings.md) doc-comment.

Yes, I have been following this RFC almost since its beginning. I still used the simpler regular commenting style to maintain consistency with us not using any doc-comments yet.

created_at: 2025-05-16T17:04:38Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,161 @@
+# Provides a consistent target interface, minimizing boilerplate and
+# automatically safeguarding declarations related to disabled options.
+#
+# This function returns the module body, not an actual module.
+#
+# The /modules/<MODULE>/<PLATFORM>.nix modules should use this function as
path: stylix/mk-target.nix
user: login: trueNAHO
body: Since this documentation is not necessarily rendered into Markdown (if we do not use doc-comments), I chose the simpler plain text formatting. However, I am also fine with this.
created_at: 2025-05-16T17:08:22Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,161 @@
+# Provides a consistent target interface, minimizing boilerplate and
+# automatically safeguarding declarations related to disabled options.
+#
+# This function returns the module body, not an actual module.
+#
+# The /modules/<MODULE>/<PLATFORM>.nix modules should use this function as
+# follows:
+#
+#     { mkTarget, lib... }:
+#     mkTarget {
+#       name = "example";
+#       humanName = "Example Target";
+#
+#       generalConfig =
+#         lib.mkIf complexCondition {
+#           home.packages = [ pkgs.hello ];
+#         };
+#
+#       configElements = [
+#         { programs.example.theme.name = "stylix"; }
+#
+#         (
+#           { colors }:
+#           {
+#             programs.example.theme.background = colors.base00;
+#           }
+#         )
+#
+#         (
+#           { fonts }:
+#           {
+#             programs.example.font.name = fonts.monospace.name;
+#           }
+#         )
+#       ];
+#     }
+#
+# Note that `mkTarget` is provided in the same place as module arguments.
+#
+# Ideally, in the future, this function also prevents unguarded module arguments
+# by limiting the arguments passed through `/stylix/autoload.nix`
path: stylix/mk-target.nix
user: login: trueNAHO
body: > Is this comment more appropriate in autoload.nix? Since that is where the restriction will be added, not here.

I placed this TODO comment in the `mkTarget` documentation because it applies to `mkTarget`. This avoids the general scalability problem where the general consumer (`/stylix/autoload.nix`) is littered with TODO comments beyond what the consumer should care about. Although this scalability issue will likely never happen in Stylix, this is the general reasoning behind why I put it in the `mkTarget` documentation.

created_at: 2025-05-16T17:12:44Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,161 @@
+# Provides a consistent target interface, minimizing boilerplate and
+# automatically safeguarding declarations related to disabled options.
+#
+# This function returns the module body, not an actual module.
+#
+# The /modules/<MODULE>/<PLATFORM>.nix modules should use this function as
+# follows:
+#
+#     { mkTarget, lib... }:
+#     mkTarget {
+#       name = "example";
+#       humanName = "Example Target";
+#
+#       generalConfig =
+#         lib.mkIf complexCondition {
+#           home.packages = [ pkgs.hello ];
+#         };
+#
+#       configElements = [
+#         { programs.example.theme.name = "stylix"; }
+#
+#         (
+#           { colors }:
+#           {
+#             programs.example.theme.background = colors.base00;
+#           }
+#         )
+#
+#         (
+#           { fonts }:
+#           {
+#             programs.example.font.name = fonts.monospace.name;
+#           }
+#         )
+#       ];
+#     }
+#
+# Note that `mkTarget` is provided in the same place as module arguments.
+#
+# Ideally, in the future, this function also prevents unguarded module arguments
+# by limiting the arguments passed through `/stylix/autoload.nix`
+{
+  # The target name used to generate options in the stylix.targets.${name}
+  # namespace.
+  name,
+
+  # The descriptive target name passed to the lib.mkEnableOption function when
+  # generating the stylix.targets.${name}.enable option.
+  humanName,
+
+  # Whether the target should be automatically enabled by default according to
+  # the stylix.autoEnable option.
+  #
+  # This should be disabled if manual setup is required or if auto-enabling
+  # causes issues.
+  autoEnable ? true,
+
+  # Additional options to be added in the stylix.targets.${name} namespace along
+  # the stylix.targets.${name}.enable option.
+  #
+  # For example, an extension guard used in the configuration can be declared as
+  # follows:
+  #
+  #     { extension.enable = lib.mkEnableOption "the bloated dependency"; }
+  extraOptions ? { },
+
+  # Configuration functions that are automatically safeguarded when any of their
+  # arguments is disabled. The provided 'cfg' argument conveniently aliases to
+  # config.stylix.targets.${name}.
+  #
+  # For example, the following configuration is not merged if the stylix.colors
+  # option is disabled:
+  #
+  #     (
+  #       { colors }:
+  #       {
+  #         programs.example.theme.background = colors.base00;
+  #       }
+  #     )
+  #
+  # The 'cfg' alias can be accessed as follows:
+  #
+  #     (
+  #       { cfg }:
+  #       {
+  #         programs.example.extension.enable = cfg.extension.enable;
+  #       }
+  #     )
+  configElements ? [ ],
+
+  # This argument mirrors the configElements argument but intentionally lacks
+  # automatic safeguarding and should only be used for complex configurations
+  # where configElements is unsuitable.
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args,
+# while using this as their module body
+let
+  module =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+
+      # Get the list of function de-structured argument names.
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+
+      getStylixAttrs =
+        fn:
+        lib.genAttrs (functionArgNames fn) (
+          arg:
+          if arg == "cfg" then
+            cfg
+          else if arg == "colors" then
+            config.lib.stylix.colors
+          else
+            config.stylix.${arg}
+              or (throw "stylix: Stylix argument requested by mkTarget does not exist: ${arg}")
path: stylix/mk-target.nix
user: login: trueNAHO
body: > Would this flow better with the name inline?
>
> ```suggestion
>               or (throw "stylix: Stylix argument `${arg}` requested by mkTarget does not exist")
> ```

Although this flows better, it makes the message non-trivially parsable. When the message has only one parameter, adding it at the end with a `: ` field separator is the convention.

> Is it worth printing the available names?

Yes, informative error messages are always appreciated.

> ```suggestion
>               or (throw "stylix: Stylix argument `${arg}` requested by mkTarget does not exist. Expected one of `cfg`, `colors`, ${
>                 lib.concatMapStringsSep ", " (name: "`${name}`") (builtins.attrNames config.stylix)
>               }")
> ```

What about the `expected <EXPECTATION>, got <REALITY>` format:

```suggestion
              or (throw "stylix: mkTarget expected one of `cfg`, `colors`, ${
                lib.concatMapStringsSep
                ", "
                (name: "`${name}`")
                (builtins.attrNames config.stylix)
              }, got ${arg}")
```

> If you'd rather quote the names instead of backticks, you can use `builtins.toJSON` to render a quoted string.

I would also be fine without any quotation. But either way is fine for me.

created_at: 2025-05-16T17:21:34Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,161 @@
+# Provides a consistent target interface, minimizing boilerplate and
path: stylix/mk-target.nix
user: login: trueNAHO
body: On second thought, since we essentially have no function documentations there is not really any consistency to uphold. In that case, using doc-comments from the start would be better.

created_at: 2025-05-16T20:22:13Z
author_association: MEMBER
diff_hunk: @@ -34,33 +34,43 @@ folder, using any name which is not on the list above.
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`. This is
-similar to
-[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
-from the standard library, however it integrates with
-[`stylix.enable`](./options/nixos.md#stylixenable) and
-[`stylix.autoEnable`](./options/nixos.md#stylixautoenable) and generates more
-specific documentation.
+Modules should be created using the `mkTarget` function whenever possible (see
+the `/stylix/mk-target.nix` documentation for more details):
 
-A general format for modules is shown below.
+```nix
+{ config, lib, mkTarget ... }:
+mkTarget {
+  name = "example";
+  humanName = "Example Target";
+
+  configElements =
+    { colors }:
+    {
+      programs.example.theme.background = colors.base00;
+    };
+}
+```
+
path: docs/src/modules.md
user: login: MattSturgeon
body: Is this the only place external contributors will commit code?

Even so, I still feel we should point out that it isn't available to normal modules.

It's possible a target module will import other modules in its directory, e.g. for refactoring complex logic. Such imported modules would not be able to specify a `mkTarget` module arg.

Also, contributors used to target modules may find it surprising they can't use `mkTarget` if they decide to submit a change elsewhere in Stylix, e.g. core features.

I agree we shouldn't get into the weeds or labour the point in these docs, but IMO it should be briefly highlighted because it is so unusual.
created_at: 2025-05-16T20:57:46Z
author_association: MEMBER
diff_hunk: @@ -8,7 +8,45 @@ builtins.concatLists (
     path: kind:
     let
       file = "${inputs.self}/modules/${path}/${for}.nix";
+      module = import file;
+
+      # Detect whether the file's value has an argument named `mkTarget`
+      useMkTarget =
+        builtins.isFunction module && (builtins.functionArgs module) ? mkTarget;
+
+      # NOTE: `mkTarget` cannot be distributed normally through the module system
+      # due to issues of infinite recursion.
+      mkTarget = import ./mk-target.nix;
     in
-    lib.optional (kind == "directory" && builtins.pathExists file) file
+    lib.optional (kind == "directory" && builtins.pathExists file) (
+      if useMkTarget then
+        { config, ... }@args:
+        let
+          # Based on `lib.modules.applyModuleArgs`
+          #
+          # Apply `mkTarget` as a special arg without actually using `specialArgs`,
+          # which cannot be defined from within a configuration.
+          context =
+            name: ''while evaluating the module argument `${name}' in "${toString file}":'';
+          extraArgs = lib.pipe module [
+            builtins.functionArgs
+            (lib.flip builtins.removeAttrs [ "mkTarget" ])
+            (builtins.mapAttrs (
+              name: _:
+              builtins.addErrorContext (context name) (
+                args.${name} or config._module.args.${name}
+              )
+            ))
+          ];
+          module = module (args // extraArgs // { inherit mkTarget; });
path: stylix/autoload.nix
user: login: MattSturgeon
body: This is inf-recursion (`module = module` within a recursive scope).

Need to either choose another name for one of the bindings, or inline this expression into the `imports` list below.
created_at: 2025-05-17T01:16:42Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,219 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
+  ```
+
+  # Examples
+
+  This function returns the module body, not an actual module.
+
+  The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
+  follows:
+
+      { mkTarget, lib... }:
+      mkTarget {
+        name = "«name»";
+        humanName = "«human readable name»";
+
+        generalConfig =
+          lib.mkIf complexCondition {
+            home.packages = [ pkgs.hello ];
+          };
+
+        configElements = [
+          { programs.«name».theme.name = "stylix"; }
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+          (
+            { fonts }:
+            {
+              programs.«name».font.name = fonts.monospace.name;
+            }
+          )
+        ];
+      }
+
+  Note that `mkTarget` is provided in the same place as module arguments.
+
+  Ideally, in the future, this function returns an actual module by better
+  integrating with the /stylix/autoload.nix logic, allowing the following target
+  simplification and preventing access to unguarded module arguments by
+  requiring /modules/<MODULE>/<PLATFORM>.nix files to be attribute sets instead
+  of modules:
+
+      {
+        name = "«name»";
+        humanName = "«human readable name»";
+
+        generalConfig =
+          { lib, pkgs }:
+          lib.mkIf complexCondition {
+            home.packages = [ pkgs.hello ];
+          };
+
+        configElements = [
+          { programs.«name».theme.name = "stylix"; }
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+          (
+            { fonts }:
+            {
+              programs.«name».font.name = fonts.monospace.name;
+            }
+          )
+        ];
+      }
+
+  # Inputs
+
+  : `name`
+    : The target name used to generate options in the stylix.targets.${name}
+      namespace.
+
+  : `humanName`
+    : The descriptive target name passed to the lib.mkEnableOption function
+      when generating the stylix.targets.${name}.enable option.
+
+  : `autoEnable`
+    : Whether the target should be automatically enabled by default according
+      to the stylix.autoEnable option.
+
+      This should be disabled if manual setup is required or if auto-enabling\
+      causes issues.
+
+  : `extraOptions`
+    : Additional options to be added in the stylix.targets.${name} namespace
+      along the stylix.targets.${name}.enable option.
+
+      For example, an extension guard used in the configuration can be declared
+      as follows:
+      { extension.enable = lib.mkEnableOption "the bloated dependency"; }
+
+  : `configElements`
+    : Configuration functions that are automatically safeguarded when any of
+      their arguments is disabled. The provided 'cfg' argument conveniently
+      aliases to config.stylix.targets.${name}.
+
+      For example, the following configuration is not merged if the stylix
+      colors option is disabled:
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+      The 'cfg' alias can be accessed as follows:
+
+          (
+            { cfg }:
+            {
+              programs.«name».extension.enable = cfg.extension.enable;
+            }
+          )
+  : `generalConfig`
+    : This argument mirrors the configElements argument but intentionally lacks
+      automatic safeguarding and should only be used for complex configurations
+      where configElements is unsuitable.
+
+  # Outputs
+
+  A nix Module body, using the "plain attrs" syntax.
+  This form should be used directly after your module args.
path: stylix/mk-target.nix
user: login: MattSturgeon
body: I know this was my wording, but it feels really clunky.

Ideas welcome, otherwise maybe we should skip the "outputs" section?
created_at: 2025-05-17T01:18:53Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,219 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
+  ```
+
+  # Examples
+
+  This function returns the module body, not an actual module.
+
+  The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
+  follows:
+
+      { mkTarget, lib... }:
+      mkTarget {
+        name = "«name»";
+        humanName = "«human readable name»";
+
+        generalConfig =
+          lib.mkIf complexCondition {
+            home.packages = [ pkgs.hello ];
+          };
+
+        configElements = [
+          { programs.«name».theme.name = "stylix"; }
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+          (
+            { fonts }:
+            {
+              programs.«name».font.name = fonts.monospace.name;
+            }
+          )
+        ];
+      }
+
+  Note that `mkTarget` is provided in the same place as module arguments.
+
+  Ideally, in the future, this function returns an actual module by better
+  integrating with the /stylix/autoload.nix logic, allowing the following target
+  simplification and preventing access to unguarded module arguments by
+  requiring /modules/<MODULE>/<PLATFORM>.nix files to be attribute sets instead
+  of modules:
+
+      {
+        name = "«name»";
+        humanName = "«human readable name»";
+
+        generalConfig =
+          { lib, pkgs }:
+          lib.mkIf complexCondition {
+            home.packages = [ pkgs.hello ];
+          };
+
+        configElements = [
+          { programs.«name».theme.name = "stylix"; }
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+          (
+            { fonts }:
+            {
+              programs.«name».font.name = fonts.monospace.name;
+            }
+          )
+        ];
+      }
+
+  # Inputs
+
+  : `name`
+    : The target name used to generate options in the stylix.targets.${name}
+      namespace.
+
+  : `humanName`
+    : The descriptive target name passed to the lib.mkEnableOption function
+      when generating the stylix.targets.${name}.enable option.
+
+  : `autoEnable`
+    : Whether the target should be automatically enabled by default according
+      to the stylix.autoEnable option.
+
+      This should be disabled if manual setup is required or if auto-enabling\
+      causes issues.
+
+  : `extraOptions`
+    : Additional options to be added in the stylix.targets.${name} namespace
+      along the stylix.targets.${name}.enable option.
+
+      For example, an extension guard used in the configuration can be declared
+      as follows:
+      { extension.enable = lib.mkEnableOption "the bloated dependency"; }
+
+  : `configElements`
+    : Configuration functions that are automatically safeguarded when any of
+      their arguments is disabled. The provided 'cfg' argument conveniently
+      aliases to config.stylix.targets.${name}.
+
+      For example, the following configuration is not merged if the stylix
+      colors option is disabled:
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+      The 'cfg' alias can be accessed as follows:
+
+          (
+            { cfg }:
+            {
+              programs.«name».extension.enable = cfg.extension.enable;
+            }
+          )
+  : `generalConfig`
+    : This argument mirrors the configElements argument but intentionally lacks
+      automatic safeguarding and should only be used for complex configurations
+      where configElements is unsuitable.
+
+  # Outputs
+
+  A nix Module body, using the "plain attrs" syntax.
+  This form should be used directly after your module args.
+*/
+{
+  name,
+  humanName,
+  autoEnable ? true,
+  extraOptions ? { },
+  configElements ? [ ],
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args,
+# while using this as their module body
+let
+  module =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+
+      # Get the list of function de-structured argument names.
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+
+      getStylixAttrs =
+        fn:
+        lib.genAttrs (functionArgNames fn) (
+          arg:
+          if arg == "cfg" then
+            cfg
+          else if arg == "colors" then
+            config.lib.stylix.colors
+          else
+            config.stylix.${arg}
+              or (throw "stylix: mkTarget expected one of `cfg`, `colors`, ${
+                lib.concatMapStringsSep ", " (name: "`${name}`") (
+                  builtins.attrNames config.stylix
+                )
+              }, got ${arg}")
path: stylix/mk-target.nix
user: login: MattSturgeon
body: ```suggestion
              }, but got: ${arg}")
```

```suggestion
              }, but found: ${arg}")
```

```suggestion
              }, but got `${arg}`.")
```

```suggestion
              }, but found `${arg}`.")
```
created_at: 2025-05-17T01:20:29Z
author_association: MEMBER
diff_hunk: @@ -34,33 +34,43 @@ folder, using any name which is not on the list above.
 
 ## Module template
 
-All modules should have an enable option created using `mkEnableTarget`. This is
-similar to
-[`mkEnableOption`](https://nix-community.github.io/docnix/reference/lib/options/lib-options-mkenableoption/)
-from the standard library, however it integrates with
-[`stylix.enable`](./options/nixos.md#stylixenable) and
-[`stylix.autoEnable`](./options/nixos.md#stylixautoenable) and generates more
-specific documentation.
+Modules should be created using the `mkTarget` function whenever possible (see
+the `/stylix/mk-target.nix` documentation for more details):
 
-A general format for modules is shown below.
+```nix
+{ config, lib, mkTarget ... }:
+mkTarget {
+  name = "example";
+  humanName = "Example Target";
+
+  configElements =
+    { colors }:
+    {
+      programs.example.theme.background = colors.base00;
+    };
+}
+```
+
path: docs/src/modules.md
user: login: trueNAHO
body: > It's possible a target module will import other modules in its directory, e.g. for refactoring complex logic. Such imported modules would not be able to specify a `mkTarget` module arg.
>
> Also, contributors used to target modules may find it surprising they can't use `mkTarget` if they decide to submit a change elsewhere in Stylix, e.g. core features.
>
> I agree we shouldn't get into the weeds or labour the point in these docs, but IMO it should be briefly highlighted because it is so unusual.

Makes sense, although this part is already briefly mentioned in the `mkTarget` source-documentation. I would just wrap your suggestion at 80 characters:

```suggestion

> [!IMPORTANT]
> The `mkTarget` argument is only available to modules imported by Stylix's
> [autoload system](https://github.com/danth/stylix/blob/-/stylix/autoload.nix),
> e.g., `modules/«target»/«platform».nix` modules.
>
> I.e., it is not available to normal modules imported via the `imports` list.

```

created_at: 2025-05-18T16:27:00Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,219 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
path: stylix/mk-target.nix
user: login: trueNAHO
body: Is `ModuleBody` a conventional "type"? Otherwise, maybe we should break the general

````markdown
# Type

```
<FUNCTION> :: <INPUT> -> <OUTPUT>
```
````

format into something where we define or explain `ModuleBody`:

- ````markdown
  # Type

  A `ModuleBody` is [...].

  ```
  <FUNCTION> :: <INPUT> -> <OUTPUT>
  ```
  ````

- ````markdown
  # Type

  ```
  <FUNCTION> :: <INPUT> -> <OUTPUT>
  ```

  where `ModuleBody` is [...].
  ````

- ````markdown
  # Type

  ```
  <FUNCTION> :: <INPUT> -> <OUTPUT>
  ```

  where:

  - `ModuleBody`
      - [...]
  ````

created_at: 2025-05-18T16:34:01Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,219 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
+  ```
+
+  # Examples
+
+  This function returns the module body, not an actual module.
path: stylix/mk-target.nix
user: login: trueNAHO
body: This sentence does not seem to belong in the "Examples" section. This sentence could be used to explain the `ModuleBody` "type" in the "Type" section.
created_at: 2025-05-18T16:35:34Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,219 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
+  ```
+
+  # Examples
+
+  This function returns the module body, not an actual module.
+
+  The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
+  follows:
+
+      { mkTarget, lib... }:
+      mkTarget {
+        name = "«name»";
+        humanName = "«human readable name»";
+
+        generalConfig =
+          lib.mkIf complexCondition {
+            home.packages = [ pkgs.hello ];
+          };
+
+        configElements = [
+          { programs.«name».theme.name = "stylix"; }
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+          (
+            { fonts }:
+            {
+              programs.«name».font.name = fonts.monospace.name;
+            }
+          )
+        ];
+      }
+
+  Note that `mkTarget` is provided in the same place as module arguments.
path: stylix/mk-target.nix
user: login: trueNAHO
body: This sentence should not be in the "Exmaples" section. Moving it to the synopsis is also not good. Maybe we should use a dedicated section, like "Environment"?
created_at: 2025-05-18T16:39:47Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,219 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
+  ```
+
+  # Examples
+
+  This function returns the module body, not an actual module.
+
+  The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
+  follows:
+
+      { mkTarget, lib... }:
+      mkTarget {
+        name = "«name»";
+        humanName = "«human readable name»";
+
+        generalConfig =
+          lib.mkIf complexCondition {
+            home.packages = [ pkgs.hello ];
+          };
+
+        configElements = [
+          { programs.«name».theme.name = "stylix"; }
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+          (
+            { fonts }:
+            {
+              programs.«name».font.name = fonts.monospace.name;
+            }
+          )
+        ];
+      }
+
+  Note that `mkTarget` is provided in the same place as module arguments.
+
+  Ideally, in the future, this function returns an actual module by better
+  integrating with the /stylix/autoload.nix logic, allowing the following target
+  simplification and preventing access to unguarded module arguments by
+  requiring /modules/<MODULE>/<PLATFORM>.nix files to be attribute sets instead
+  of modules:
+
+      {
+        name = "«name»";
+        humanName = "«human readable name»";
+
+        generalConfig =
+          { lib, pkgs }:
+          lib.mkIf complexCondition {
+            home.packages = [ pkgs.hello ];
+          };
+
+        configElements = [
+          { programs.«name».theme.name = "stylix"; }
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+          (
+            { fonts }:
+            {
+              programs.«name».font.name = fonts.monospace.name;
+            }
+          )
+        ];
+      }
path: stylix/mk-target.nix
user: login: trueNAHO
body: This could go into a "Future Work" section.
created_at: 2025-05-18T16:40:04Z
author_association: MEMBER
path: stylix/mk-target.nix
user: login: trueNAHO
body: Based on my comments about adding sections, I would sort them as:

1. Type
2. Examples
3. Inputs
4. Environment
5. Future Work

created_at: 2025-05-18T16:41:29Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,219 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
+  ```
+
+  # Examples
+
+  This function returns the module body, not an actual module.
+
+  The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
+  follows:
+
+      { mkTarget, lib... }:
+      mkTarget {
+        name = "«name»";
+        humanName = "«human readable name»";
+
+        generalConfig =
+          lib.mkIf complexCondition {
+            home.packages = [ pkgs.hello ];
+          };
+
+        configElements = [
+          { programs.«name».theme.name = "stylix"; }
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+          (
+            { fonts }:
+            {
+              programs.«name».font.name = fonts.monospace.name;
+            }
+          )
+        ];
+      }
+
+  Note that `mkTarget` is provided in the same place as module arguments.
+
+  Ideally, in the future, this function returns an actual module by better
+  integrating with the /stylix/autoload.nix logic, allowing the following target
+  simplification and preventing access to unguarded module arguments by
+  requiring /modules/<MODULE>/<PLATFORM>.nix files to be attribute sets instead
+  of modules:
+
+      {
+        name = "«name»";
+        humanName = "«human readable name»";
+
+        generalConfig =
+          { lib, pkgs }:
+          lib.mkIf complexCondition {
+            home.packages = [ pkgs.hello ];
+          };
+
+        configElements = [
+          { programs.«name».theme.name = "stylix"; }
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+          (
+            { fonts }:
+            {
+              programs.«name».font.name = fonts.monospace.name;
+            }
+          )
+        ];
+      }
+
+  # Inputs
+
+  : `name`
+    : The target name used to generate options in the stylix.targets.${name}
+      namespace.
+
+  : `humanName`
+    : The descriptive target name passed to the lib.mkEnableOption function
+      when generating the stylix.targets.${name}.enable option.
+
+  : `autoEnable`
+    : Whether the target should be automatically enabled by default according
+      to the stylix.autoEnable option.
+
+      This should be disabled if manual setup is required or if auto-enabling\
+      causes issues.
+
+  : `extraOptions`
+    : Additional options to be added in the stylix.targets.${name} namespace
+      along the stylix.targets.${name}.enable option.
+
+      For example, an extension guard used in the configuration can be declared
+      as follows:
+      { extension.enable = lib.mkEnableOption "the bloated dependency"; }
+
+  : `configElements`
+    : Configuration functions that are automatically safeguarded when any of
+      their arguments is disabled. The provided 'cfg' argument conveniently
+      aliases to config.stylix.targets.${name}.
+
+      For example, the following configuration is not merged if the stylix
+      colors option is disabled:
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+      The 'cfg' alias can be accessed as follows:
+
+          (
+            { cfg }:
+            {
+              programs.«name».extension.enable = cfg.extension.enable;
+            }
+          )
+  : `generalConfig`
+    : This argument mirrors the configElements argument but intentionally lacks
+      automatic safeguarding and should only be used for complex configurations
+      where configElements is unsuitable.
+
+  # Outputs
+
+  A nix Module body, using the "plain attrs" syntax.
+  This form should be used directly after your module args.
path: stylix/mk-target.nix
user: login: trueNAHO
body: >  maybe we should skip the "outputs" section?

Should this section be removed if we explain the `ModuleBody` "type" in the "Type" section?
created_at: 2025-05-18T16:44:03Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,219 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
+  ```
+
+  # Examples
+
+  This function returns the module body, not an actual module.
+
+  The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
+  follows:
+
+      { mkTarget, lib... }:
+      mkTarget {
+        name = "«name»";
+        humanName = "«human readable name»";
+
+        generalConfig =
+          lib.mkIf complexCondition {
+            home.packages = [ pkgs.hello ];
+          };
+
+        configElements = [
+          { programs.«name».theme.name = "stylix"; }
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+          (
+            { fonts }:
+            {
+              programs.«name».font.name = fonts.monospace.name;
+            }
+          )
+        ];
+      }
+
+  Note that `mkTarget` is provided in the same place as module arguments.
+
+  Ideally, in the future, this function returns an actual module by better
+  integrating with the /stylix/autoload.nix logic, allowing the following target
+  simplification and preventing access to unguarded module arguments by
+  requiring /modules/<MODULE>/<PLATFORM>.nix files to be attribute sets instead
+  of modules:
+
+      {
+        name = "«name»";
+        humanName = "«human readable name»";
+
+        generalConfig =
+          { lib, pkgs }:
+          lib.mkIf complexCondition {
+            home.packages = [ pkgs.hello ];
+          };
+
+        configElements = [
+          { programs.«name».theme.name = "stylix"; }
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+          (
+            { fonts }:
+            {
+              programs.«name».font.name = fonts.monospace.name;
+            }
+          )
+        ];
+      }
+
+  # Inputs
+
+  : `name`
+    : The target name used to generate options in the stylix.targets.${name}
+      namespace.
+
+  : `humanName`
+    : The descriptive target name passed to the lib.mkEnableOption function
+      when generating the stylix.targets.${name}.enable option.
+
+  : `autoEnable`
+    : Whether the target should be automatically enabled by default according
+      to the stylix.autoEnable option.
+
+      This should be disabled if manual setup is required or if auto-enabling\
path: stylix/mk-target.nix
user: login: trueNAHO
body: ```suggestion
      This should be disabled if manual setup is required or if auto-enabling
```
created_at: 2025-05-18T16:45:26Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,219 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
+  ```
+
+  # Examples
+
+  This function returns the module body, not an actual module.
+
+  The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
+  follows:
+
+      { mkTarget, lib... }:
+      mkTarget {
+        name = "«name»";
+        humanName = "«human readable name»";
+
+        generalConfig =
+          lib.mkIf complexCondition {
+            home.packages = [ pkgs.hello ];
+          };
+
+        configElements = [
+          { programs.«name».theme.name = "stylix"; }
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+          (
+            { fonts }:
+            {
+              programs.«name».font.name = fonts.monospace.name;
+            }
+          )
+        ];
+      }
+
+  Note that `mkTarget` is provided in the same place as module arguments.
+
+  Ideally, in the future, this function returns an actual module by better
+  integrating with the /stylix/autoload.nix logic, allowing the following target
+  simplification and preventing access to unguarded module arguments by
+  requiring /modules/<MODULE>/<PLATFORM>.nix files to be attribute sets instead
+  of modules:
+
+      {
+        name = "«name»";
+        humanName = "«human readable name»";
+
+        generalConfig =
+          { lib, pkgs }:
+          lib.mkIf complexCondition {
+            home.packages = [ pkgs.hello ];
+          };
+
+        configElements = [
+          { programs.«name».theme.name = "stylix"; }
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+          (
+            { fonts }:
+            {
+              programs.«name».font.name = fonts.monospace.name;
+            }
+          )
+        ];
+      }
+
+  # Inputs
+
+  : `name`
+    : The target name used to generate options in the stylix.targets.${name}
+      namespace.
+
+  : `humanName`
+    : The descriptive target name passed to the lib.mkEnableOption function
+      when generating the stylix.targets.${name}.enable option.
+
+  : `autoEnable`
+    : Whether the target should be automatically enabled by default according
+      to the stylix.autoEnable option.
+
+      This should be disabled if manual setup is required or if auto-enabling\
+      causes issues.
+
+  : `extraOptions`
+    : Additional options to be added in the stylix.targets.${name} namespace
+      along the stylix.targets.${name}.enable option.
+
+      For example, an extension guard used in the configuration can be declared
+      as follows:
+      { extension.enable = lib.mkEnableOption "the bloated dependency"; }
path: stylix/mk-target.nix
user: login: trueNAHO
body: If these blocks are plain text, this should be indented:

```suggestion
          { extension.enable = lib.mkEnableOption "the bloated dependency"; }
```

If these blocks are markdown, this should be formatted as a code block:

```suggestion
      ```nix
      { extension.enable = lib.mkEnableOption "the bloated dependency"; }
      ```
```

If the language is by default `nix`, we can drop it:

```suggestion
      ```
      { extension.enable = lib.mkEnableOption "the bloated dependency"; }
      ```
```

Unsure whether the doc-comment RFC specifies blocks having a default language.

created_at: 2025-05-18T16:49:09Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,219 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
+  ```
+
+  # Examples
+
+  This function returns the module body, not an actual module.
+
+  The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
+  follows:
+
+      { mkTarget, lib... }:
+      mkTarget {
+        name = "«name»";
+        humanName = "«human readable name»";
+
+        generalConfig =
+          lib.mkIf complexCondition {
+            home.packages = [ pkgs.hello ];
+          };
+
+        configElements = [
+          { programs.«name».theme.name = "stylix"; }
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+          (
+            { fonts }:
+            {
+              programs.«name».font.name = fonts.monospace.name;
+            }
+          )
+        ];
+      }
+
+  Note that `mkTarget` is provided in the same place as module arguments.
+
+  Ideally, in the future, this function returns an actual module by better
+  integrating with the /stylix/autoload.nix logic, allowing the following target
+  simplification and preventing access to unguarded module arguments by
+  requiring /modules/<MODULE>/<PLATFORM>.nix files to be attribute sets instead
+  of modules:
+
+      {
+        name = "«name»";
+        humanName = "«human readable name»";
+
+        generalConfig =
+          { lib, pkgs }:
+          lib.mkIf complexCondition {
+            home.packages = [ pkgs.hello ];
+          };
+
+        configElements = [
+          { programs.«name».theme.name = "stylix"; }
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+          (
+            { fonts }:
+            {
+              programs.«name».font.name = fonts.monospace.name;
+            }
+          )
+        ];
+      }
+
+  # Inputs
+
+  : `name`
+    : The target name used to generate options in the stylix.targets.${name}
+      namespace.
+
+  : `humanName`
+    : The descriptive target name passed to the lib.mkEnableOption function
+      when generating the stylix.targets.${name}.enable option.
+
+  : `autoEnable`
+    : Whether the target should be automatically enabled by default according
+      to the stylix.autoEnable option.
+
+      This should be disabled if manual setup is required or if auto-enabling\
+      causes issues.
+
+  : `extraOptions`
+    : Additional options to be added in the stylix.targets.${name} namespace
+      along the stylix.targets.${name}.enable option.
+
+      For example, an extension guard used in the configuration can be declared
+      as follows:
+      { extension.enable = lib.mkEnableOption "the bloated dependency"; }
+
+  : `configElements`
+    : Configuration functions that are automatically safeguarded when any of
+      their arguments is disabled. The provided 'cfg' argument conveniently
+      aliases to config.stylix.targets.${name}.
+
+      For example, the following configuration is not merged if the stylix
+      colors option is disabled:
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+      The 'cfg' alias can be accessed as follows:
+
+          (
+            { cfg }:
+            {
+              programs.«name».extension.enable = cfg.extension.enable;
+            }
+          )
+  : `generalConfig`
+    : This argument mirrors the configElements argument but intentionally lacks
+      automatic safeguarding and should only be used for complex configurations
+      where configElements is unsuitable.
path: stylix/mk-target.nix
user: login: trueNAHO
body: If this is markdown, then

```md
stylix.targets.${name}
```

instances should be formatted as:

```md
`stylix.targets.${name}`
```

created_at: 2025-05-18T16:51:22Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,219 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
+  ```
+
+  # Examples
+
+  This function returns the module body, not an actual module.
+
+  The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
+  follows:
+
+      { mkTarget, lib... }:
+      mkTarget {
+        name = "«name»";
+        humanName = "«human readable name»";
+
+        generalConfig =
+          lib.mkIf complexCondition {
+            home.packages = [ pkgs.hello ];
+          };
+
+        configElements = [
+          { programs.«name».theme.name = "stylix"; }
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+          (
+            { fonts }:
+            {
+              programs.«name».font.name = fonts.monospace.name;
+            }
+          )
+        ];
+      }
+
+  Note that `mkTarget` is provided in the same place as module arguments.
+
+  Ideally, in the future, this function returns an actual module by better
+  integrating with the /stylix/autoload.nix logic, allowing the following target
+  simplification and preventing access to unguarded module arguments by
+  requiring /modules/<MODULE>/<PLATFORM>.nix files to be attribute sets instead
+  of modules:
+
+      {
+        name = "«name»";
+        humanName = "«human readable name»";
+
+        generalConfig =
+          { lib, pkgs }:
+          lib.mkIf complexCondition {
+            home.packages = [ pkgs.hello ];
+          };
+
+        configElements = [
+          { programs.«name».theme.name = "stylix"; }
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+          (
+            { fonts }:
+            {
+              programs.«name».font.name = fonts.monospace.name;
+            }
+          )
+        ];
+      }
+
+  # Inputs
+
+  : `name`
+    : The target name used to generate options in the stylix.targets.${name}
+      namespace.
+
+  : `humanName`
+    : The descriptive target name passed to the lib.mkEnableOption function
+      when generating the stylix.targets.${name}.enable option.
+
+  : `autoEnable`
+    : Whether the target should be automatically enabled by default according
+      to the stylix.autoEnable option.
+
+      This should be disabled if manual setup is required or if auto-enabling\
+      causes issues.
+
+  : `extraOptions`
+    : Additional options to be added in the stylix.targets.${name} namespace
+      along the stylix.targets.${name}.enable option.
+
+      For example, an extension guard used in the configuration can be declared
+      as follows:
+      { extension.enable = lib.mkEnableOption "the bloated dependency"; }
+
+  : `configElements`
+    : Configuration functions that are automatically safeguarded when any of
+      their arguments is disabled. The provided 'cfg' argument conveniently
+      aliases to config.stylix.targets.${name}.
+
+      For example, the following configuration is not merged if the stylix
+      colors option is disabled:
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
path: stylix/mk-target.nix
user: login: trueNAHO
body: Consider block formatting this.
created_at: 2025-05-18T16:51:39Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,219 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
+  ```
+
+  # Examples
+
+  This function returns the module body, not an actual module.
+
+  The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
+  follows:
+
+      { mkTarget, lib... }:
+      mkTarget {
+        name = "«name»";
+        humanName = "«human readable name»";
+
+        generalConfig =
+          lib.mkIf complexCondition {
+            home.packages = [ pkgs.hello ];
+          };
+
+        configElements = [
+          { programs.«name».theme.name = "stylix"; }
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+          (
+            { fonts }:
+            {
+              programs.«name».font.name = fonts.monospace.name;
+            }
+          )
+        ];
+      }
+
+  Note that `mkTarget` is provided in the same place as module arguments.
+
+  Ideally, in the future, this function returns an actual module by better
+  integrating with the /stylix/autoload.nix logic, allowing the following target
+  simplification and preventing access to unguarded module arguments by
+  requiring /modules/<MODULE>/<PLATFORM>.nix files to be attribute sets instead
+  of modules:
+
+      {
+        name = "«name»";
+        humanName = "«human readable name»";
+
+        generalConfig =
+          { lib, pkgs }:
+          lib.mkIf complexCondition {
+            home.packages = [ pkgs.hello ];
+          };
+
+        configElements = [
+          { programs.«name».theme.name = "stylix"; }
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+          (
+            { fonts }:
+            {
+              programs.«name».font.name = fonts.monospace.name;
+            }
+          )
+        ];
+      }
+
+  # Inputs
+
+  : `name`
+    : The target name used to generate options in the stylix.targets.${name}
+      namespace.
+
+  : `humanName`
+    : The descriptive target name passed to the lib.mkEnableOption function
+      when generating the stylix.targets.${name}.enable option.
+
+  : `autoEnable`
+    : Whether the target should be automatically enabled by default according
+      to the stylix.autoEnable option.
+
+      This should be disabled if manual setup is required or if auto-enabling\
+      causes issues.
+
+  : `extraOptions`
+    : Additional options to be added in the stylix.targets.${name} namespace
+      along the stylix.targets.${name}.enable option.
+
+      For example, an extension guard used in the configuration can be declared
+      as follows:
+      { extension.enable = lib.mkEnableOption "the bloated dependency"; }
+
+  : `configElements`
+    : Configuration functions that are automatically safeguarded when any of
+      their arguments is disabled. The provided 'cfg' argument conveniently
+      aliases to config.stylix.targets.${name}.
+
+      For example, the following configuration is not merged if the stylix
+      colors option is disabled:
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+      The 'cfg' alias can be accessed as follows:
+
+          (
+            { cfg }:
+            {
+              programs.«name».extension.enable = cfg.extension.enable;
+            }
+          )
path: stylix/mk-target.nix
user: login: trueNAHO
body: Consider block formatting this.
created_at: 2025-05-18T16:51:53Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,219 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
+  ```
+
+  # Examples
+
+  This function returns the module body, not an actual module.
+
+  The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
+  follows:
+
+      { mkTarget, lib... }:
+      mkTarget {
+        name = "«name»";
+        humanName = "«human readable name»";
+
+        generalConfig =
+          lib.mkIf complexCondition {
+            home.packages = [ pkgs.hello ];
+          };
+
+        configElements = [
+          { programs.«name».theme.name = "stylix"; }
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+          (
+            { fonts }:
+            {
+              programs.«name».font.name = fonts.monospace.name;
+            }
+          )
+        ];
+      }
+
+  Note that `mkTarget` is provided in the same place as module arguments.
+
+  Ideally, in the future, this function returns an actual module by better
+  integrating with the /stylix/autoload.nix logic, allowing the following target
+  simplification and preventing access to unguarded module arguments by
+  requiring /modules/<MODULE>/<PLATFORM>.nix files to be attribute sets instead
+  of modules:
+
+      {
+        name = "«name»";
+        humanName = "«human readable name»";
+
+        generalConfig =
+          { lib, pkgs }:
+          lib.mkIf complexCondition {
+            home.packages = [ pkgs.hello ];
+          };
+
+        configElements = [
+          { programs.«name».theme.name = "stylix"; }
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+          (
+            { fonts }:
+            {
+              programs.«name».font.name = fonts.monospace.name;
+            }
+          )
+        ];
+      }
+
+  # Inputs
+
+  : `name`
+    : The target name used to generate options in the stylix.targets.${name}
+      namespace.
+
+  : `humanName`
+    : The descriptive target name passed to the lib.mkEnableOption function
+      when generating the stylix.targets.${name}.enable option.
+
+  : `autoEnable`
+    : Whether the target should be automatically enabled by default according
+      to the stylix.autoEnable option.
+
+      This should be disabled if manual setup is required or if auto-enabling\
+      causes issues.
+
+  : `extraOptions`
+    : Additional options to be added in the stylix.targets.${name} namespace
+      along the stylix.targets.${name}.enable option.
+
+      For example, an extension guard used in the configuration can be declared
+      as follows:
+      { extension.enable = lib.mkEnableOption "the bloated dependency"; }
+
+  : `configElements`
+    : Configuration functions that are automatically safeguarded when any of
+      their arguments is disabled. The provided 'cfg' argument conveniently
+      aliases to config.stylix.targets.${name}.
+
+      For example, the following configuration is not merged if the stylix
+      colors option is disabled:
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+      The 'cfg' alias can be accessed as follows:
+
+          (
+            { cfg }:
+            {
+              programs.«name».extension.enable = cfg.extension.enable;
+            }
+          )
+  : `generalConfig`
path: stylix/mk-target.nix
user: login: trueNAHO
body: ```suggestion

  : `generalConfig`
```
created_at: 2025-05-18T16:51:59Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,219 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
+  ```
+
+  # Examples
+
+  This function returns the module body, not an actual module.
+
+  The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
+  follows:
+
+      { mkTarget, lib... }:
+      mkTarget {
+        name = "«name»";
+        humanName = "«human readable name»";
+
+        generalConfig =
+          lib.mkIf complexCondition {
+            home.packages = [ pkgs.hello ];
+          };
+
+        configElements = [
+          { programs.«name».theme.name = "stylix"; }
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+          (
+            { fonts }:
+            {
+              programs.«name».font.name = fonts.monospace.name;
+            }
+          )
+        ];
+      }
+
+  Note that `mkTarget` is provided in the same place as module arguments.
+
+  Ideally, in the future, this function returns an actual module by better
+  integrating with the /stylix/autoload.nix logic, allowing the following target
+  simplification and preventing access to unguarded module arguments by
+  requiring /modules/<MODULE>/<PLATFORM>.nix files to be attribute sets instead
+  of modules:
+
+      {
+        name = "«name»";
+        humanName = "«human readable name»";
+
+        generalConfig =
+          { lib, pkgs }:
+          lib.mkIf complexCondition {
+            home.packages = [ pkgs.hello ];
+          };
+
+        configElements = [
+          { programs.«name».theme.name = "stylix"; }
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+          (
+            { fonts }:
+            {
+              programs.«name».font.name = fonts.monospace.name;
+            }
+          )
+        ];
+      }
+
+  # Inputs
+
+  : `name`
+    : The target name used to generate options in the stylix.targets.${name}
+      namespace.
+
+  : `humanName`
+    : The descriptive target name passed to the lib.mkEnableOption function
+      when generating the stylix.targets.${name}.enable option.
+
+  : `autoEnable`
+    : Whether the target should be automatically enabled by default according
+      to the stylix.autoEnable option.
+
+      This should be disabled if manual setup is required or if auto-enabling\
+      causes issues.
+
+  : `extraOptions`
+    : Additional options to be added in the stylix.targets.${name} namespace
+      along the stylix.targets.${name}.enable option.
+
+      For example, an extension guard used in the configuration can be declared
+      as follows:
+      { extension.enable = lib.mkEnableOption "the bloated dependency"; }
+
+  : `configElements`
+    : Configuration functions that are automatically safeguarded when any of
+      their arguments is disabled. The provided 'cfg' argument conveniently
+      aliases to config.stylix.targets.${name}.
+
+      For example, the following configuration is not merged if the stylix
+      colors option is disabled:
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+      The 'cfg' alias can be accessed as follows:
+
+          (
+            { cfg }:
+            {
+              programs.«name».extension.enable = cfg.extension.enable;
+            }
+          )
+  : `generalConfig`
+    : This argument mirrors the configElements argument but intentionally lacks
+      automatic safeguarding and should only be used for complex configurations
+      where configElements is unsuitable.
+
+  # Outputs
+
+  A nix Module body, using the "plain attrs" syntax.
+  This form should be used directly after your module args.
+*/
+{
+  name,
+  humanName,
+  autoEnable ? true,
+  extraOptions ? { },
+  configElements ? [ ],
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args,
+# while using this as their module body
+let
+  module =
+    { config, lib, ... }:
+    let
+      cfg = config.stylix.targets.${name};
+
+      # Get the list of function de-structured argument names.
+      functionArgNames =
+        fn:
+        lib.pipe fn [
+          lib.functionArgs
+          builtins.attrNames
+        ];
+
+      getStylixAttrs =
+        fn:
+        lib.genAttrs (functionArgNames fn) (
+          arg:
+          if arg == "cfg" then
+            cfg
+          else if arg == "colors" then
+            config.lib.stylix.colors
+          else
+            config.stylix.${arg}
+              or (throw "stylix: mkTarget expected one of `cfg`, `colors`, ${
+                lib.concatMapStringsSep ", " (name: "`${name}`") (
+                  builtins.attrNames config.stylix
+                )
+              }, got ${arg}")
path: stylix/mk-target.nix
user: login: trueNAHO
body: Good point. Considering the long list of items, adding `but` adds clarity.

I prefer:

> ```suggestion
>               }, but got: ${arg}")
> ```

created_at: 2025-05-18T16:53:44Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,219 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
path: stylix/mk-target.nix
user: login: MattSturgeon
body: > Is ModuleBody a conventional \"type\"?

No. However my understanding of "**Type**" is that it doesn't need to be 100% formal. For example, you aren't limited to nix-language types, it is also ok to use custom types by name, such as `_type`-tagged attrsets for example.

Strictly speaking, `Module` is the most accurate. However that doesn't specify which form of module (plain attrs, fn to attrs, file containing either of those). This function is unusual in that returning only the plain attrs "body" of a module is an explicit part of its contract.

If you think it warrants further explanation, that's fine too. Something like this?:

````md
# Type

```
mkTarget :: AttrSet -> ModuleBody
```

Where `ModuleBody` is a module that doesn't take any arguments. This allows the caller to use module arguments.
````

As a tangent, it's also common for "**Type**" to show the structured attrs specified in the function signature, although this is kinda redundant with "**Inputs**" and can get hard to maintain when there's a lot of arguments:

````md
# Type

```
mkTarget :: { name :: String, humanName :: String, ... } -> Module
```

````
created_at: 2025-05-18T19:35:27Z
author_association: MEMBER
path: stylix/mk-target.nix
user: login: MattSturgeon
body: Let's be careful not to mix internal "TODOs" into documentation. They should be separate and not user(contributor)-facing

It is possible to have both a doc-comment and normal comments bound to a function if needed, and `:doc` and `nixdoc` will usually still pick up the doc-comment.
created_at: 2025-05-18T19:38:00Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,219 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
+  ```
+
+  # Examples
+
+  This function returns the module body, not an actual module.
+
+  The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
+  follows:
+
+      { mkTarget, lib... }:
+      mkTarget {
+        name = "«name»";
+        humanName = "«human readable name»";
+
+        generalConfig =
+          lib.mkIf complexCondition {
+            home.packages = [ pkgs.hello ];
+          };
+
+        configElements = [
+          { programs.«name».theme.name = "stylix"; }
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+          (
+            { fonts }:
+            {
+              programs.«name».font.name = fonts.monospace.name;
+            }
+          )
+        ];
+      }
+
+  Note that `mkTarget` is provided in the same place as module arguments.
+
+  Ideally, in the future, this function returns an actual module by better
+  integrating with the /stylix/autoload.nix logic, allowing the following target
+  simplification and preventing access to unguarded module arguments by
+  requiring /modules/<MODULE>/<PLATFORM>.nix files to be attribute sets instead
+  of modules:
+
+      {
+        name = "«name»";
+        humanName = "«human readable name»";
+
+        generalConfig =
+          { lib, pkgs }:
+          lib.mkIf complexCondition {
+            home.packages = [ pkgs.hello ];
+          };
+
+        configElements = [
+          { programs.«name».theme.name = "stylix"; }
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+          (
+            { fonts }:
+            {
+              programs.«name».font.name = fonts.monospace.name;
+            }
+          )
+        ];
+      }
+
+  # Inputs
+
+  : `name`
+    : The target name used to generate options in the stylix.targets.${name}
+      namespace.
+
+  : `humanName`
+    : The descriptive target name passed to the lib.mkEnableOption function
+      when generating the stylix.targets.${name}.enable option.
+
+  : `autoEnable`
+    : Whether the target should be automatically enabled by default according
+      to the stylix.autoEnable option.
+
+      This should be disabled if manual setup is required or if auto-enabling\
+      causes issues.
+
+  : `extraOptions`
+    : Additional options to be added in the stylix.targets.${name} namespace
+      along the stylix.targets.${name}.enable option.
+
+      For example, an extension guard used in the configuration can be declared
+      as follows:
+      { extension.enable = lib.mkEnableOption "the bloated dependency"; }
path: stylix/mk-target.nix
user: login: MattSturgeon
body: It does not. The default is no highlighting.

Note that if you ever add `nixdoc` generated docs to your website, it will spit out the markdown more or less as-is; so it'll be mdbook rendering it.

As for `nix repl`, currently it doesn't do much (any?) syntax highlighting, but it does render a limited amount of markdown.

In nixpkgs, it is convention to use ```` ```nix ```` for examples, and ```` ``` ```` for types.
created_at: 2025-05-18T19:41:30Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,219 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
+  ```
+
+  # Examples
+
+  This function returns the module body, not an actual module.
+
+  The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
+  follows:
+
+      { mkTarget, lib... }:
+      mkTarget {
+        name = "«name»";
+        humanName = "«human readable name»";
+
+        generalConfig =
+          lib.mkIf complexCondition {
+            home.packages = [ pkgs.hello ];
+          };
+
+        configElements = [
+          { programs.«name».theme.name = "stylix"; }
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+          (
+            { fonts }:
+            {
+              programs.«name».font.name = fonts.monospace.name;
+            }
+          )
+        ];
+      }
+
+  Note that `mkTarget` is provided in the same place as module arguments.
+
+  Ideally, in the future, this function returns an actual module by better
+  integrating with the /stylix/autoload.nix logic, allowing the following target
+  simplification and preventing access to unguarded module arguments by
+  requiring /modules/<MODULE>/<PLATFORM>.nix files to be attribute sets instead
+  of modules:
+
+      {
+        name = "«name»";
+        humanName = "«human readable name»";
+
+        generalConfig =
+          { lib, pkgs }:
+          lib.mkIf complexCondition {
+            home.packages = [ pkgs.hello ];
+          };
+
+        configElements = [
+          { programs.«name».theme.name = "stylix"; }
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
+
+          (
+            { fonts }:
+            {
+              programs.«name».font.name = fonts.monospace.name;
+            }
+          )
+        ];
+      }
+
+  # Inputs
+
+  : `name`
+    : The target name used to generate options in the stylix.targets.${name}
+      namespace.
+
+  : `humanName`
+    : The descriptive target name passed to the lib.mkEnableOption function
+      when generating the stylix.targets.${name}.enable option.
+
+  : `autoEnable`
+    : Whether the target should be automatically enabled by default according
+      to the stylix.autoEnable option.
+
+      This should be disabled if manual setup is required or if auto-enabling\
+      causes issues.
+
+  : `extraOptions`
+    : Additional options to be added in the stylix.targets.${name} namespace
+      along the stylix.targets.${name}.enable option.
+
+      For example, an extension guard used in the configuration can be declared
+      as follows:
+      { extension.enable = lib.mkEnableOption "the bloated dependency"; }
+
+  : `configElements`
+    : Configuration functions that are automatically safeguarded when any of
+      their arguments is disabled. The provided 'cfg' argument conveniently
+      aliases to config.stylix.targets.${name}.
+
+      For example, the following configuration is not merged if the stylix
+      colors option is disabled:
+
+          (
+            { colors }:
+            {
+              programs.«name».theme.background = colors.base00;
+            }
+          )
path: stylix/mk-target.nix
user: login: MattSturgeon
body: Should be fine to use ```` ```nix ```` fenced code within the definition list.

If the fenced code block is indented at the same level as the definition list term, it should render as part of that [`<dd>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/dd) term.
created_at: 2025-05-19T01:25:17Z
author_association: MEMBER
path: stylix/mk-target.nix
user: login: Flameopathic
body: I have reorganized the documentation to include the first four sections in the doc comment and the fifth in a regular comment.
created_at: 2025-05-19T01:26:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,225 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
+  ```
+
+  Where `ModuleBody` is a module that doesn't take any arguments. This allows
+  the caller to use module arguments.
+
+  # Examples
+
+  The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
+  follows:
+  ```nix
+  { mkTarget, lib... }:
+  mkTarget {
+    name = "«name»";
+    humanName = "«human readable name»";
+
+    generalConfig =
+      lib.mkIf complexCondition {
+        home.packages = [ pkgs.hello ];
+      };
+
+    configElements = [
+      { programs.«name».theme.name = "stylix"; }
+
+      (
+        { colors }:
+        {
+          programs.«name».theme.background = colors.base00;
+        }
+      )
+
+      (
+        { fonts }:
+        {
+          programs.«name».font.name = fonts.monospace.name;
+        }
+      )
+    ];
+  }
+  ```
+
+  # Inputs
+
+  : `name`
+    : The target name used to generate options in the `stylix.targets.${name}`
+      namespace.
+
+  : `humanName`
+    : The descriptive target name passed to the lib.mkEnableOption function
+      when generating the `stylix.targets.${name}.enable` option.
+
+  : `autoEnable`
+    : Whether the target should be automatically enabled by default according
+      to the `stylix.autoEnable` option.
+
+      This should be disabled if manual setup is required or if auto-enabling
+      causes issues.
+
+  : `extraOptions`
+    : Additional options to be added in the `stylix.targets.${name}` namespace
+      along the `stylix.targets.${name}.enable` option.
+
+      For example, an extension guard used in the configuration can be declared
+      as follows:
+      ```nix
+      { extension.enable = lib.mkEnableOption "the bloated dependency"; }
+      ```
+
+  : `configElements`
+    : Configuration functions that are automatically safeguarded when any of
+      their arguments is disabled. The provided 'cfg' argument conveniently
+      aliases to `config.stylix.targets.${name}`.
+
+      For example, the following configuration is not merged if the stylix
+      colors option is null:
+      ```nix
+      (
+        { colors }:
+        {
+          programs.«name».theme.background = colors.base00;
+        }
+      )
+      ```
+
+      The 'cfg' alias can be accessed as follows:
+      ```nix
+      (
+        { cfg }:
+        {
+          programs.«name».extension.enable = cfg.extension.enable;
+        }
+      )
+      ```
+
+  : `generalConfig`
+    : This argument mirrors the `configElements` argument but intentionally
+      lacks automatic safeguarding and should only be used for complex
+      configurations where `configElements` is unsuitable.
+
+  # Environment
+
+  The function is provided alongside module arguments in any modules imported
+  through `/stylix/autoload.nix`.
+*/
+# Future work:
path: stylix/mk-target.nix
user: login: MattSturgeon
body: For my own sanity, I double checked this is still picked up by `nix repl`'s `:doc`:

```
$ nix repl
Nix 2.28.3
Type :? for help.

nix-repl> mkTarget = import ./stylix/mk-target.nix

nix-repl> :doc mkTarget
Function defined at ./stylix/mk-target.nix:148:1

    Provides a consistent target interface, minimizing boilerplate and automatically safeguarding declarations related to disabled options.

Type

      | mkTarget :: AttrSet -> ModuleBody

    Where ModuleBody is a module that doesn't take any arguments. This allows the caller to use module arguments.

Examples

    The modules/«MODULE»/«PLATFORM».nix modules should use this function as follows:

      | { mkTarget, lib... }:
      | mkTarget {
      |   name = "«name»";
      |   humanName = "«human readable name»";
      |
      |   generalConfig =
      |     lib.mkIf complexCondition {
      |       home.packages = [ pkgs.hello ];
      |     };
      |
      |   configElements = [
      |     { programs.«name».theme.name = "stylix"; }
      |
      |     (
      |       { colors }:
      |       {
      |         programs.«name».theme.background = colors.base00;
      |       }
      |     )
      |
      |     (
      |       { fonts }:
      |       {
      |         programs.«name».font.name = fonts.monospace.name;
      |       }
      |     )
      |   ];
      | }

Inputs

    : name

      : The target name used to generate options in the stylix.targets.${name} namespace.

      : humanName
          : The descriptive target name passed to the lib.mkEnableOption function when generating the stylix.targets.${name}.enable option.

      : autoEnable
          : Whether the target should be automatically enabled by default according to the stylix.autoEnable option.

        This should be disabled if manual setup is required or if auto-enabling causes issues.

      : extraOptions
          : Additional options to be added in the stylix.targets.${name} namespace along the stylix.targets.${name}.enable option.

        For example, an extension guard used in the configuration can be declared as follows: nix { extension.enable = lib.mkEnableOption "the bloated dependency"; }

      : configElements
          : Configuration functions that are automatically safeguarded when any of their arguments is disabled. The provided 'cfg' argument conveniently aliases to config.stylix.targets.${name}.

        For example, the following configuration is not merged if the stylix colors option is null:

          | (
          |   { colors }:
          |   {
          |     programs.«name».theme.background = colors.base00;
          |   }
          | )

        The 'cfg' alias can be accessed as follows:

          | (
          |   { cfg }:
          |   {
          |     programs.«name».extension.enable = cfg.extension.enable;
          |   }
          | )

      : generalConfig
          : This argument mirrors the configElements argument but intentionally lacks automatic safeguarding and should only be used for complex configurations where configElements is unsuitable.

Environment

    The function is provided alongside module arguments in any modules imported through /stylix/autoload.nix.
```

<details><summary>Screenshot with coloured render</summary>
<p>

![image](https://github.com/user-attachments/assets/54cbdd45-b288-44d2-a715-c93c556d1349)


</p>
</details> 

I also checked with [nixdoc](https://github.com/nix-community/nixdoc):

<details><summary>Output from <code>nixdoc</code></summary>
<p>

`````
$ nix run nixpkgs#nixdoc -- \
    --file ./stylix/mk-target.nix \
    --prefix config.lib \
    --category stylix \
    --description Title

# Title {#sec-functions-library-stylix}
Provides a consistent target interface, minimizing boilerplate and
automatically safeguarding declarations related to disabled options.

## Type

```
mkTarget :: AttrSet -> ModuleBody
```

Where `ModuleBody` is a module that doesn't take any arguments. This allows
the caller to use module arguments.

## Examples

The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
follows:
```nix
{ mkTarget, lib... }:
mkTarget {
  name = "«name»";
  humanName = "«human readable name»";

  generalConfig =
    lib.mkIf complexCondition {
      home.packages = [ pkgs.hello ];
    };

  configElements = [
    { programs.«name».theme.name = "stylix"; }

    (
      { colors }:
      {
        programs.«name».theme.background = colors.base00;
      }
    )

    (
      { fonts }:
      {
        programs.«name».font.name = fonts.monospace.name;
      }
    )
  ];
}
```

## Inputs

: `name`
  : The target name used to generate options in the `stylix.targets.${name}`
    namespace.

: `humanName`
  : The descriptive target name passed to the lib.mkEnableOption function
    when generating the `stylix.targets.${name}.enable` option.

: `autoEnable`
  : Whether the target should be automatically enabled by default according
    to the `stylix.autoEnable` option.

    This should be disabled if manual setup is required or if auto-enabling
    causes issues.

: `extraOptions`
  : Additional options to be added in the `stylix.targets.${name}` namespace
    along the `stylix.targets.${name}.enable` option.

    For example, an extension guard used in the configuration can be declared
    as follows:
    ```nix
    { extension.enable = lib.mkEnableOption "the bloated dependency"; }
    ```

: `configElements`
  : Configuration functions that are automatically safeguarded when any of
    their arguments is disabled. The provided 'cfg' argument conveniently
    aliases to `config.stylix.targets.${name}`.

    For example, the following configuration is not merged if the stylix
    colors option is null:
    ```nix
    (
      { colors }:
      {
        programs.«name».theme.background = colors.base00;
      }
    )
    ```

    The 'cfg' alias can be accessed as follows:
    ```nix
    (
      { cfg }:
      {
        programs.«name».extension.enable = cfg.extension.enable;
      }
    )
    ```

: `generalConfig`
  : This argument mirrors the `configElements` argument but intentionally
    lacks automatic safeguarding and should only be used for complex
    configurations where `configElements` is unsuitable.

## Environment

The function is provided alongside module arguments in any modules imported
through `/stylix/autoload.nix`.
`````

</p>
</details> 

(Note `nixdoc` doesn't know the function's name because it is designed to read a file that contains an attrset of functions)
created_at: 2025-05-19T01:37:59Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,225 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
+  ```
+
+  Where `ModuleBody` is a module that doesn't take any arguments. This allows
+  the caller to use module arguments.
+
+  # Examples
+
+  The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
+  follows:
+  ```nix
+  { mkTarget, lib... }:
+  mkTarget {
+    name = "«name»";
+    humanName = "«human readable name»";
+
+    generalConfig =
+      lib.mkIf complexCondition {
+        home.packages = [ pkgs.hello ];
+      };
+
+    configElements = [
+      { programs.«name».theme.name = "stylix"; }
+
+      (
+        { colors }:
+        {
+          programs.«name».theme.background = colors.base00;
+        }
+      )
+
+      (
+        { fonts }:
+        {
+          programs.«name».font.name = fonts.monospace.name;
+        }
+      )
+    ];
+  }
+  ```
+
+  # Inputs
+
+  : `name`
+    : The target name used to generate options in the `stylix.targets.${name}`
+      namespace.
+
+  : `humanName`
+    : The descriptive target name passed to the lib.mkEnableOption function
+      when generating the `stylix.targets.${name}.enable` option.
+
+  : `autoEnable`
+    : Whether the target should be automatically enabled by default according
+      to the `stylix.autoEnable` option.
+
+      This should be disabled if manual setup is required or if auto-enabling
+      causes issues.
+
+  : `extraOptions`
+    : Additional options to be added in the `stylix.targets.${name}` namespace
+      along the `stylix.targets.${name}.enable` option.
+
+      For example, an extension guard used in the configuration can be declared
+      as follows:
+      ```nix
+      { extension.enable = lib.mkEnableOption "the bloated dependency"; }
+      ```
+
+  : `configElements`
+    : Configuration functions that are automatically safeguarded when any of
+      their arguments is disabled. The provided 'cfg' argument conveniently
+      aliases to `config.stylix.targets.${name}`.
+
+      For example, the following configuration is not merged if the stylix
+      colors option is null:
+      ```nix
+      (
+        { colors }:
+        {
+          programs.«name».theme.background = colors.base00;
+        }
+      )
+      ```
+
+      The 'cfg' alias can be accessed as follows:
+      ```nix
+      (
+        { cfg }:
+        {
+          programs.«name».extension.enable = cfg.extension.enable;
+        }
+      )
+      ```
path: stylix/mk-target.nix
user: login: MattSturgeon
body: `:doc` isn't rendering this correctly, because it is using a strict markdown that requires blank lines either side of the code block.

This fixes it:

```diff
diff --git a/stylix/mk-target.nix b/stylix/mk-target.nix
index 7d59a42..ce187c5 100644
--- a/stylix/mk-target.nix
+++ b/stylix/mk-target.nix
@@ -15,6 +15,7 @@
 
   The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
   follows:
+
   ```nix
   { mkTarget, lib... }:
   mkTarget {
@@ -80,6 +81,7 @@
 
       For example, the following configuration is not merged if the stylix
       colors option is null:
+
       ```nix
       (
         { colors }:
@@ -90,6 +92,7 @@
       ```
 
       The 'cfg' alias can be accessed as follows:
+
       ```nix
       (
         { cfg }:
@@ -115,6 +118,7 @@
 # target simplification and preventing access to unguarded module arguments by
 # requiring `/modules/<MODULE>/<PLATFORM>.nix` files to be attribute sets
 # instead of modules:
+#
 # ```nix
 # {
 #   name = "«name»";
```
created_at: 2025-05-19T01:45:18Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,229 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
+  ```
+
+  Where `ModuleBody` is a module that doesn't take any arguments. This allows
+  the caller to use module arguments.
+
+  # Examples
+
+  The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
+  follows:
+
+  ```nix
+  { mkTarget, lib... }:
+  mkTarget {
+    name = "«name»";
+    humanName = "«human readable name»";
+
+    generalConfig =
+      lib.mkIf complexCondition {
+        home.packages = [ pkgs.hello ];
+      };
+
+    configElements = [
+      { programs.«name».theme.name = "stylix"; }
+
+      (
+        { colors }:
+        {
+          programs.«name».theme.background = colors.base00;
+        }
+      )
+
+      (
+        { fonts }:
+        {
+          programs.«name».font.name = fonts.monospace.name;
+        }
+      )
+    ];
+  }
+  ```
+
+  # Inputs
+
+  : `name`
+    : The target name used to generate options in the `stylix.targets.${name}`
+      namespace.
path: stylix/mk-target.nix
user: login: MattSturgeon
body: I get this wrong a lot too,[^1] but the actual definition list syntax doesn't have a `:` on the list "term" ([`<dt>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/dt)), only on the "details" ([`<dd>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/dd)).

See https://github.com/NixOS/nixpkgs/blob/master/doc/README.md#function-arguments and https://github.com/NixOS/nixpkgs/blob/master/doc/README.md#definition-lists.

```nix
  *`name`* (String)
  : The target name used to generate options in the `stylix.targets.${name}`
    namespace.
```

Note however, in this case these are actually children of the function's arg, so should be:

```md
  *`config`* (Attribute set)

  : *`name`* (String)

    : The target name used to generate options in the `stylix.targets.${name}`
      namespace.

    *`humanName`* (String)

    : The descriptive target name passed to the lib.mkEnableOption function
      when generating the `stylix.targets.${name}.enable` option.

    *`extraOptions`* (Attribute set)

    : Additional options to be added in the `stylix.targets.${name}` namespace
      along the `stylix.targets.${name}.enable` option.
```

[^1]: Just pushed the [exact same fix](https://github.com/NixOS/nixpkgs/compare/c6a960e96014ca6efdbeac61c75691e894c6e184..d833d962865ff4e8e38a7c13efd0fbf94ac32f90#diff-87b941e79d67b6a89c4bf73452ec896f0881e9c2367e5d2c4f064d9520c16c3f) to one of my [PRs](https://github.com/NixOS/nixpkgs/pull/405317)!
created_at: 2025-05-19T02:09:25Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,229 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
+  ```
+
+  Where `ModuleBody` is a module that doesn't take any arguments. This allows
+  the caller to use module arguments.
+
+  # Examples
+
+  The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
+  follows:
+
+  ```nix
+  { mkTarget, lib... }:
+  mkTarget {
+    name = "«name»";
+    humanName = "«human readable name»";
+
+    generalConfig =
+      lib.mkIf complexCondition {
+        home.packages = [ pkgs.hello ];
+      };
+
+    configElements = [
+      { programs.«name».theme.name = "stylix"; }
+
+      (
+        { colors }:
+        {
+          programs.«name».theme.background = colors.base00;
+        }
+      )
+
+      (
+        { fonts }:
+        {
+          programs.«name».font.name = fonts.monospace.name;
+        }
+      )
+    ];
+  }
+  ```
+
+  # Inputs
+
+  : `name`
+    : The target name used to generate options in the `stylix.targets.${name}`
+      namespace.
path: stylix/mk-target.nix
user: login: MattSturgeon
body: Side-note:

I'm not convinced the \*emphasis\* is needed, but it is recommended in the nixpkgs docs-readme.

Anecdotally, I've not seen it in many _real_ doc-comments.

Testing in the repl, it looks pretty bad with my colourscheme...

Here the first few are \*emphasised\* and the last few are not:

![image](https://github.com/user-attachments/assets/99852a21-885d-4458-958b-3355d5ace697)

created_at: 2025-05-19T02:16:34Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,231 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
+  ```
+
+  Where `ModuleBody` is a module that doesn't take any arguments. This allows
+  the caller to use module arguments.
+
+  # Examples
+
+  The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
+  follows:
+
+  ```nix
+  { mkTarget, lib... }:
+  mkTarget {
+    name = "«name»";
+    humanName = "«human readable name»";
+
+    generalConfig =
+      lib.mkIf complexCondition {
+        home.packages = [ pkgs.hello ];
+      };
+
+    configElements = [
+      { programs.«name».theme.name = "stylix"; }
+
+      (
+        { colors }:
+        {
+          programs.«name».theme.background = colors.base00;
+        }
+      )
+
+      (
+        { fonts }:
+        {
+          programs.«name».font.name = fonts.monospace.name;
+        }
+      )
+    ];
+  }
+  ```
+
+  # Inputs
+
+  `config` (Attribute set)
+
+  : `name` (String)
path: stylix/mk-target.nix
user: login: trueNAHO
body: I suppose this is undesired:

```suggestion
    `name` (String)
```
created_at: 2025-05-19T16:25:31Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,231 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
+  ```
+
+  Where `ModuleBody` is a module that doesn't take any arguments. This allows
+  the caller to use module arguments.
+
+  # Examples
+
+  The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
+  follows:
+
+  ```nix
+  { mkTarget, lib... }:
+  mkTarget {
+    name = "«name»";
+    humanName = "«human readable name»";
+
+    generalConfig =
+      lib.mkIf complexCondition {
+        home.packages = [ pkgs.hello ];
+      };
+
+    configElements = [
+      { programs.«name».theme.name = "stylix"; }
+
+      (
+        { colors }:
+        {
+          programs.«name».theme.background = colors.base00;
+        }
+      )
+
+      (
+        { fonts }:
+        {
+          programs.«name».font.name = fonts.monospace.name;
+        }
+      )
+    ];
+  }
+  ```
+
+  # Inputs
+
+  `config` (Attribute set)
path: stylix/mk-target.nix
user: login: trueNAHO
body: I assume this is the convention for notating that the function receives only one argument of type attribute set. And then this argument receives some arbitrary indicative name, like `config`.
created_at: 2025-05-19T16:28:05Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,231 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
+  ```
+
+  Where `ModuleBody` is a module that doesn't take any arguments. This allows
+  the caller to use module arguments.
+
+  # Examples
+
+  The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
+  follows:
+
+  ```nix
+  { mkTarget, lib... }:
+  mkTarget {
+    name = "«name»";
+    humanName = "«human readable name»";
+
+    generalConfig =
+      lib.mkIf complexCondition {
+        home.packages = [ pkgs.hello ];
+      };
+
+    configElements = [
+      { programs.«name».theme.name = "stylix"; }
+
+      (
+        { colors }:
+        {
+          programs.«name».theme.background = colors.base00;
+        }
+      )
+
+      (
+        { fonts }:
+        {
+          programs.«name».font.name = fonts.monospace.name;
+        }
+      )
+    ];
+  }
+  ```
+
+  # Inputs
+
+  `config` (Attribute set)
+
+  : `name` (String)
+    : The target name used to generate options in the `stylix.targets.${name}`
+      namespace.
+
+    `humanName` (String)
+    : The descriptive target name passed to the lib.mkEnableOption function
+      when generating the `stylix.targets.${name}.enable` option.
+
+    `autoEnable` (Boolean)
+    : Whether the target should be automatically enabled by default according
+      to the `stylix.autoEnable` option.
+
+      This should be disabled if manual setup is required or if auto-enabling
+      causes issues.
+
+    `extraOptions` (Attribute set)
+    : Additional options to be added in the `stylix.targets.${name}` namespace
+      along the `stylix.targets.${name}.enable` option.
+
+      For example, an extension guard used in the configuration can be declared
+      as follows:
+      ```nix
+      { extension.enable = lib.mkEnableOption "the bloated dependency"; }
+      ```
+
+    `configElements` (List or attribute set or function)
+    : Configuration functions that are automatically safeguarded when any of
+      their arguments is disabled. The provided 'cfg' argument conveniently
path: stylix/mk-target.nix
user: login: trueNAHO
body: Since we are using Markdown and not just plain text:

```suggestion
      their arguments is disabled. The provided `cfg` argument conveniently
```
created_at: 2025-05-19T16:30:06Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,231 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
+  ```
+
+  Where `ModuleBody` is a module that doesn't take any arguments. This allows
+  the caller to use module arguments.
+
+  # Examples
+
+  The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
+  follows:
+
+  ```nix
+  { mkTarget, lib... }:
+  mkTarget {
+    name = "«name»";
+    humanName = "«human readable name»";
+
+    generalConfig =
+      lib.mkIf complexCondition {
+        home.packages = [ pkgs.hello ];
+      };
+
+    configElements = [
+      { programs.«name».theme.name = "stylix"; }
+
+      (
+        { colors }:
+        {
+          programs.«name».theme.background = colors.base00;
+        }
+      )
+
+      (
+        { fonts }:
+        {
+          programs.«name».font.name = fonts.monospace.name;
+        }
+      )
+    ];
+  }
+  ```
+
+  # Inputs
+
+  `config` (Attribute set)
+
+  : `name` (String)
+    : The target name used to generate options in the `stylix.targets.${name}`
+      namespace.
+
+    `humanName` (String)
+    : The descriptive target name passed to the lib.mkEnableOption function
+      when generating the `stylix.targets.${name}.enable` option.
+
+    `autoEnable` (Boolean)
+    : Whether the target should be automatically enabled by default according
+      to the `stylix.autoEnable` option.
+
+      This should be disabled if manual setup is required or if auto-enabling
+      causes issues.
+
+    `extraOptions` (Attribute set)
+    : Additional options to be added in the `stylix.targets.${name}` namespace
+      along the `stylix.targets.${name}.enable` option.
+
+      For example, an extension guard used in the configuration can be declared
+      as follows:
+      ```nix
+      { extension.enable = lib.mkEnableOption "the bloated dependency"; }
+      ```
+
+    `configElements` (List or attribute set or function)
+    : Configuration functions that are automatically safeguarded when any of
+      their arguments is disabled. The provided 'cfg' argument conveniently
+      aliases to `config.stylix.targets.${name}`.
+
+      For example, the following configuration is not merged if the stylix
+      colors option is null:
+
+      ```nix
+      (
+        { colors }:
+        {
+          programs.«name».theme.background = colors.base00;
+        }
+      )
+      ```
+
+      The 'cfg' alias can be accessed as follows:
path: stylix/mk-target.nix
user: login: trueNAHO
body: ```suggestion
      The `cfg` alias can be accessed as follows:
```
created_at: 2025-05-19T16:30:14Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,231 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
+  ```
+
+  Where `ModuleBody` is a module that doesn't take any arguments. This allows
+  the caller to use module arguments.
+
+  # Examples
+
+  The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
+  follows:
+
+  ```nix
+  { mkTarget, lib... }:
+  mkTarget {
+    name = "«name»";
+    humanName = "«human readable name»";
+
+    generalConfig =
+      lib.mkIf complexCondition {
+        home.packages = [ pkgs.hello ];
+      };
+
+    configElements = [
+      { programs.«name».theme.name = "stylix"; }
+
+      (
+        { colors }:
+        {
+          programs.«name».theme.background = colors.base00;
+        }
+      )
+
+      (
+        { fonts }:
+        {
+          programs.«name».font.name = fonts.monospace.name;
+        }
+      )
+    ];
+  }
+  ```
+
+  # Inputs
+
+  `config` (Attribute set)
+
+  : `name` (String)
+    : The target name used to generate options in the `stylix.targets.${name}`
+      namespace.
+
+    `humanName` (String)
+    : The descriptive target name passed to the lib.mkEnableOption function
+      when generating the `stylix.targets.${name}.enable` option.
+
+    `autoEnable` (Boolean)
+    : Whether the target should be automatically enabled by default according
+      to the `stylix.autoEnable` option.
+
+      This should be disabled if manual setup is required or if auto-enabling
+      causes issues.
+
+    `extraOptions` (Attribute set)
+    : Additional options to be added in the `stylix.targets.${name}` namespace
+      along the `stylix.targets.${name}.enable` option.
+
+      For example, an extension guard used in the configuration can be declared
+      as follows:
+      ```nix
+      { extension.enable = lib.mkEnableOption "the bloated dependency"; }
+      ```
+
+    `configElements` (List or attribute set or function)
+    : Configuration functions that are automatically safeguarded when any of
+      their arguments is disabled. The provided 'cfg' argument conveniently
+      aliases to `config.stylix.targets.${name}`.
+
+      For example, the following configuration is not merged if the stylix
+      colors option is null:
+
+      ```nix
+      (
+        { colors }:
+        {
+          programs.«name».theme.background = colors.base00;
+        }
+      )
+      ```
+
+      The 'cfg' alias can be accessed as follows:
+
+      ```nix
+      (
+        { cfg }:
+        {
+          programs.«name».extension.enable = cfg.extension.enable;
+        }
+      )
+      ```
+
+    `generalConfig` (Attribute set or function)
+    : This argument mirrors the `configElements` argument but intentionally
+      lacks automatic safeguarding and should only be used for complex
+      configurations where `configElements` is unsuitable.
+
+  # Environment
+
+  The function is provided alongside module arguments in any modules imported
+  through `/stylix/autoload.nix`.
+*/
+# Future work:
path: stylix/mk-target.nix
user: login: trueNAHO
body: The section seems overkill in a regular comment:

```suggestion
```

Unsure whether this looks better or the formatter is happy with this, but an empty line beetween the function documentation and a regular comment might be good:

```nix
*/

# Ideally, in the future, this function returns an actual module by better
```
created_at: 2025-05-19T16:32:10Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,231 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
+  ```
+
+  Where `ModuleBody` is a module that doesn't take any arguments. This allows
+  the caller to use module arguments.
+
+  # Examples
+
+  The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
+  follows:
+
+  ```nix
+  { mkTarget, lib... }:
+  mkTarget {
+    name = "«name»";
+    humanName = "«human readable name»";
+
+    generalConfig =
+      lib.mkIf complexCondition {
+        home.packages = [ pkgs.hello ];
+      };
+
+    configElements = [
+      { programs.«name».theme.name = "stylix"; }
+
+      (
+        { colors }:
+        {
+          programs.«name».theme.background = colors.base00;
+        }
+      )
+
+      (
+        { fonts }:
+        {
+          programs.«name».font.name = fonts.monospace.name;
+        }
+      )
+    ];
+  }
+  ```
+
+  # Inputs
+
+  `config` (Attribute set)
+
+  : `name` (String)
+    : The target name used to generate options in the `stylix.targets.${name}`
+      namespace.
+
+    `humanName` (String)
+    : The descriptive target name passed to the lib.mkEnableOption function
+      when generating the `stylix.targets.${name}.enable` option.
+
+    `autoEnable` (Boolean)
+    : Whether the target should be automatically enabled by default according
+      to the `stylix.autoEnable` option.
+
+      This should be disabled if manual setup is required or if auto-enabling
+      causes issues.
+
+    `extraOptions` (Attribute set)
+    : Additional options to be added in the `stylix.targets.${name}` namespace
+      along the `stylix.targets.${name}.enable` option.
+
+      For example, an extension guard used in the configuration can be declared
+      as follows:
+      ```nix
+      { extension.enable = lib.mkEnableOption "the bloated dependency"; }
+      ```
+
+    `configElements` (List or attribute set or function)
+    : Configuration functions that are automatically safeguarded when any of
+      their arguments is disabled. The provided 'cfg' argument conveniently
+      aliases to `config.stylix.targets.${name}`.
+
+      For example, the following configuration is not merged if the stylix
+      colors option is null:
+
+      ```nix
+      (
+        { colors }:
+        {
+          programs.«name».theme.background = colors.base00;
+        }
+      )
+      ```
+
+      The 'cfg' alias can be accessed as follows:
+
+      ```nix
+      (
+        { cfg }:
+        {
+          programs.«name».extension.enable = cfg.extension.enable;
+        }
+      )
+      ```
+
+    `generalConfig` (Attribute set or function)
+    : This argument mirrors the `configElements` argument but intentionally
+      lacks automatic safeguarding and should only be used for complex
+      configurations where `configElements` is unsuitable.
+
+  # Environment
+
+  The function is provided alongside module arguments in any modules imported
+  through `/stylix/autoload.nix`.
+*/
+# Future work:
+# Ideally, in the future, this function returns an actual module by better
+# integrating with the `/stylix/autoload.nix` logic, allowing the following
+# target simplification and preventing access to unguarded module arguments by
+# requiring `/modules/<MODULE>/<PLATFORM>.nix` files to be attribute sets
+# instead of modules:
+#
+# ```nix
+# {
+#   name = "«name»";
+#   humanName = "«human readable name»";
+#
+#   generalConfig =
+#     { lib, pkgs }:
+#     lib.mkIf complexCondition {
+#       home.packages = [ pkgs.hello ];
+#     };
+#
+#   configElements = [
+#     { programs.«name».theme.name = "stylix"; }
+#
+#     (
+#       { colors }:
+#       {
+#         programs.«name».theme.background = colors.base00;
+#       }
+#     )
+#
+#     (
+#       { fonts }:
+#       {
+#         programs.«name».font.name = fonts.monospace.name;
+#       }
+#     )
+#   ];
+# }
+# ```
path: stylix/mk-target.nix
user: login: trueNAHO
body: Since this no longer a Markdown comment, we could drop the Markdown syntax and use the previous plain text formatting:

```suggestion
# integrating with the /stylix/autoload.nix logic, allowing the following target
# simplification and preventing access to unguarded module arguments by
# requiring /modules/<MODULE>/<PLATFORM>.nix files to be attribute sets instead
# of modules:
#
#     {
#       name = "example";
#       humanName = "Example Target";
#
#       generalConfig =
#         { lib, pkgs }:
#         lib.mkIf complexCondition {
#           home.packages = [ pkgs.hello ];
#         };
#
#       configElements = [
#         { programs.example.theme.name = "stylix"; }
#
#         (
#           { colors }:
#           {
#             programs.example.theme.background = colors.base00;
#           }
#         )
#
#         (
#           { fonts }:
#           {
#             programs.example.font.name = fonts.monospace.name;
#           }
#         )
#       ];
#     }
```

I am fine with either approach.

created_at: 2025-05-19T16:36:39Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,231 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
+  ```
+
+  Where `ModuleBody` is a module that doesn't take any arguments. This allows
+  the caller to use module arguments.
+
+  # Examples
+
+  The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
+  follows:
+
+  ```nix
+  { mkTarget, lib... }:
+  mkTarget {
+    name = "«name»";
+    humanName = "«human readable name»";
+
+    generalConfig =
+      lib.mkIf complexCondition {
+        home.packages = [ pkgs.hello ];
+      };
+
+    configElements = [
+      { programs.«name».theme.name = "stylix"; }
+
+      (
+        { colors }:
+        {
+          programs.«name».theme.background = colors.base00;
+        }
+      )
+
+      (
+        { fonts }:
+        {
+          programs.«name».font.name = fonts.monospace.name;
+        }
+      )
+    ];
+  }
+  ```
+
+  # Inputs
+
+  `config` (Attribute set)
+
+  : `name` (String)
path: stylix/mk-target.nix
user: login: MattSturgeon
body: No, that is intended.

It indicates that this is the start of `config`'s definition details.

See https://github.com/nix-community/stylix/pull/1130#discussion_r2094729021
created_at: 2025-05-19T17:15:59Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,231 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
+  ```
+
+  Where `ModuleBody` is a module that doesn't take any arguments. This allows
+  the caller to use module arguments.
+
+  # Examples
+
+  The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
+  follows:
+
+  ```nix
+  { mkTarget, lib... }:
+  mkTarget {
+    name = "«name»";
+    humanName = "«human readable name»";
+
+    generalConfig =
+      lib.mkIf complexCondition {
+        home.packages = [ pkgs.hello ];
+      };
+
+    configElements = [
+      { programs.«name».theme.name = "stylix"; }
+
+      (
+        { colors }:
+        {
+          programs.«name».theme.background = colors.base00;
+        }
+      )
+
+      (
+        { fonts }:
+        {
+          programs.«name».font.name = fonts.monospace.name;
+        }
+      )
+    ];
+  }
+  ```
+
+  # Inputs
+
+  `config` (Attribute set)
+
+  : `name` (String)
+    : The target name used to generate options in the `stylix.targets.${name}`
+      namespace.
+
+    `humanName` (String)
+    : The descriptive target name passed to the lib.mkEnableOption function
+      when generating the `stylix.targets.${name}.enable` option.
+
+    `autoEnable` (Boolean)
+    : Whether the target should be automatically enabled by default according
+      to the `stylix.autoEnable` option.
+
+      This should be disabled if manual setup is required or if auto-enabling
+      causes issues.
+
+    `extraOptions` (Attribute set)
+    : Additional options to be added in the `stylix.targets.${name}` namespace
+      along the `stylix.targets.${name}.enable` option.
+
+      For example, an extension guard used in the configuration can be declared
+      as follows:
+      ```nix
+      { extension.enable = lib.mkEnableOption "the bloated dependency"; }
+      ```
+
+    `configElements` (List or attribute set or function)
+    : Configuration functions that are automatically safeguarded when any of
+      their arguments is disabled. The provided 'cfg' argument conveniently
+      aliases to `config.stylix.targets.${name}`.
+
+      For example, the following configuration is not merged if the stylix
+      colors option is null:
+
+      ```nix
+      (
+        { colors }:
+        {
+          programs.«name».theme.background = colors.base00;
+        }
+      )
+      ```
+
+      The 'cfg' alias can be accessed as follows:
+
+      ```nix
+      (
+        { cfg }:
+        {
+          programs.«name».extension.enable = cfg.extension.enable;
+        }
+      )
+      ```
+
+    `generalConfig` (Attribute set or function)
+    : This argument mirrors the `configElements` argument but intentionally
+      lacks automatic safeguarding and should only be used for complex
+      configurations where `configElements` is unsuitable.
+
+  # Environment
+
+  The function is provided alongside module arguments in any modules imported
+  through `/stylix/autoload.nix`.
+*/
+# Future work:
path: stylix/mk-target.nix
user: login: MattSturgeon
body: Personally I'd do:

```suggestion

# TODO:
```

But no strong feelings.

A blank line should be fine, nixfmt permits single blank lines nearly anywhere.
created_at: 2025-05-19T17:18:43Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,231 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
+  ```
+
+  Where `ModuleBody` is a module that doesn't take any arguments. This allows
+  the caller to use module arguments.
+
+  # Examples
+
+  The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
+  follows:
+
+  ```nix
+  { mkTarget, lib... }:
+  mkTarget {
+    name = "«name»";
+    humanName = "«human readable name»";
+
+    generalConfig =
+      lib.mkIf complexCondition {
+        home.packages = [ pkgs.hello ];
+      };
+
+    configElements = [
+      { programs.«name».theme.name = "stylix"; }
+
+      (
+        { colors }:
+        {
+          programs.«name».theme.background = colors.base00;
+        }
+      )
+
+      (
+        { fonts }:
+        {
+          programs.«name».font.name = fonts.monospace.name;
+        }
+      )
+    ];
+  }
+  ```
+
+  # Inputs
+
+  `config` (Attribute set)
+
+  : `name` (String)
+    : The target name used to generate options in the `stylix.targets.${name}`
+      namespace.
+
+    `humanName` (String)
+    : The descriptive target name passed to the lib.mkEnableOption function
+      when generating the `stylix.targets.${name}.enable` option.
+
+    `autoEnable` (Boolean)
+    : Whether the target should be automatically enabled by default according
+      to the `stylix.autoEnable` option.
+
+      This should be disabled if manual setup is required or if auto-enabling
+      causes issues.
+
+    `extraOptions` (Attribute set)
+    : Additional options to be added in the `stylix.targets.${name}` namespace
+      along the `stylix.targets.${name}.enable` option.
+
+      For example, an extension guard used in the configuration can be declared
+      as follows:
+      ```nix
+      { extension.enable = lib.mkEnableOption "the bloated dependency"; }
+      ```
+
+    `configElements` (List or attribute set or function)
+    : Configuration functions that are automatically safeguarded when any of
+      their arguments is disabled. The provided 'cfg' argument conveniently
+      aliases to `config.stylix.targets.${name}`.
+
+      For example, the following configuration is not merged if the stylix
+      colors option is null:
+
+      ```nix
+      (
+        { colors }:
+        {
+          programs.«name».theme.background = colors.base00;
+        }
+      )
+      ```
+
+      The 'cfg' alias can be accessed as follows:
+
+      ```nix
+      (
+        { cfg }:
+        {
+          programs.«name».extension.enable = cfg.extension.enable;
+        }
+      )
+      ```
+
+    `generalConfig` (Attribute set or function)
+    : This argument mirrors the `configElements` argument but intentionally
+      lacks automatic safeguarding and should only be used for complex
+      configurations where `configElements` is unsuitable.
+
+  # Environment
+
+  The function is provided alongside module arguments in any modules imported
+  through `/stylix/autoload.nix`.
+*/
+# Future work:
path: stylix/mk-target.nix
user: login: trueNAHO
body: > Personally I'd do:
>
> ```suggestion
>
> # TODO:
> ```

Nice. In that case, we should:

```nix
*/

# TODO: Ideally, in the future, this function returns an actual module by better
# integrating with the /stylix/autoload.nix logic, allowing the following target
# simplification and preventing access to unguarded module arguments by
# requiring /modules/<MODULE>/<PLATFORM>.nix files to be attribute sets instead
# of modules:
```

created_at: 2025-05-19T17:40:51Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,231 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
+  ```
+
+  Where `ModuleBody` is a module that doesn't take any arguments. This allows
+  the caller to use module arguments.
+
+  # Examples
+
+  The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
+  follows:
+
+  ```nix
+  { mkTarget, lib... }:
+  mkTarget {
+    name = "«name»";
+    humanName = "«human readable name»";
+
+    generalConfig =
+      lib.mkIf complexCondition {
+        home.packages = [ pkgs.hello ];
+      };
+
+    configElements = [
+      { programs.«name».theme.name = "stylix"; }
+
+      (
+        { colors }:
+        {
+          programs.«name».theme.background = colors.base00;
+        }
+      )
+
+      (
+        { fonts }:
+        {
+          programs.«name».font.name = fonts.monospace.name;
+        }
+      )
+    ];
+  }
+  ```
+
+  # Inputs
+
+  `config` (Attribute set)
+
+  : `name` (String)
path: stylix/mk-target.nix
user: login: trueNAHO
body: > No, that is intended.
>
> It indicates that this is the start of `config`'s definition details.
>
> See [#1130 (comment)](https://github.com/nix-community/stylix/pull/1130#discussion_r2094729021)

~~Is `name` not incorrectly indented:~~

> ```md
> Inputs
>
>     config (Attribute set)
>
>       : name (String)
>           : The target name used to generate options in the stylix.targets.${name} namespace.
>
>         humanName (String)
>           : The descriptive target name passed to the lib.mkEnableOption function when generating the stylix.targets.${name}.enable
>             option.
> ```

~~Or are all the other entires improperly indented?~~

Nevermind, I understand now.

created_at: 2025-05-19T17:46:09Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,229 @@
+/**
+  Provides a consistent target interface, minimizing boilerplate and
+  automatically safeguarding declarations related to disabled options.
+
+  # Type
+
+  ```
+  mkTarget :: AttrSet -> ModuleBody
+  ```
+
+  Where `ModuleBody` is a module that doesn't take any arguments. This allows
+  the caller to use module arguments.
+
+  # Examples
+
+  The `modules/«MODULE»/«PLATFORM».nix` modules should use this function as
+  follows:
+
+  ```nix
+  { mkTarget, lib... }:
+  mkTarget {
+    name = "«name»";
+    humanName = "«human readable name»";
+
+    generalConfig =
+      lib.mkIf complexCondition {
+        home.packages = [ pkgs.hello ];
+      };
+
+    configElements = [
+      { programs.«name».theme.name = "stylix"; }
+
+      (
+        { colors }:
+        {
+          programs.«name».theme.background = colors.base00;
+        }
+      )
+
+      (
+        { fonts }:
+        {
+          programs.«name».font.name = fonts.monospace.name;
+        }
+      )
+    ];
+  }
+  ```
+
+  # Inputs
+
+  `config` (Attribute set)
+
+  : `name` (String)
+    : The target name used to generate options in the `stylix.targets.${name}`
+      namespace.
+
+    `humanName` (String)
+    : The descriptive target name passed to the lib.mkEnableOption function
+      when generating the `stylix.targets.${name}.enable` option.
+
+    `autoEnable` (Boolean)
+    : Whether the target should be automatically enabled by default according
+      to the `stylix.autoEnable` option.
+
+      This should be disabled if manual setup is required or if auto-enabling
+      causes issues.
+
+    `extraOptions` (Attribute set)
+    : Additional options to be added in the `stylix.targets.${name}` namespace
+      along the `stylix.targets.${name}.enable` option.
+
+      For example, an extension guard used in the configuration can be declared
+      as follows:
+      ```nix
+      { extension.enable = lib.mkEnableOption "the bloated dependency"; }
+      ```
+
+    `configElements` (List or attribute set or function)
+    : Configuration functions that are automatically safeguarded when any of
+      their arguments is disabled. The provided `cfg` argument conveniently
+      aliases to `config.stylix.targets.${name}`.
+
+      For example, the following configuration is not merged if the stylix
+      colors option is null:
+
+      ```nix
+      (
+        { colors }:
+        {
+          programs.«name».theme.background = colors.base00;
+        }
+      )
+      ```
+
+      The `cfg` alias can be accessed as follows:
+
+      ```nix
+      (
+        { cfg }:
+        {
+          programs.«name».extension.enable = cfg.extension.enable;
+        }
+      )
+      ```
+
+    `generalConfig` (Attribute set or function)
+    : This argument mirrors the `configElements` argument but intentionally
+      lacks automatic safeguarding and should only be used for complex
+      configurations where `configElements` is unsuitable.
+
+  # Environment
+
+  The function is provided alongside module arguments in any modules imported
+  through `/stylix/autoload.nix`.
+*/
+
+# TODO: Ideally, in the future, this function returns an actual module by better
+# integrating with the /stylix/autoload.nix logic, allowing the following target
+# simplification and preventing access to unguarded module arguments by
+# requiring /modules/<MODULE>/<PLATFORM>.nix files to be attribute sets instead
+# of modules:
+#
+#     {
+#       name = "example";
+#       humanName = "Example Target";
+#
+#       generalConfig =
+#         { lib, pkgs }:
+#         lib.mkIf complexCondition {
+#           home.packages = [ pkgs.hello ];
+#         };
+#
+#       configElements = [
+#         { programs.example.theme.name = "stylix"; }
+#
+#         (
+#           { colors }:
+#           {
+#             programs.example.theme.background = colors.base00;
+#           }
+#         )
+#
+#         (
+#           { fonts }:
+#           {
+#             programs.example.font.name = fonts.monospace.name;
+#           }
+#         )
+#       ];
+#     }
+{
+  name,
+  humanName,
+  autoEnable ? true,
+  extraOptions ? { },
+  configElements ? [ ],
+  generalConfig ? null,
+}:
+# Note: we only return a plain module so that calling code can use module args,
+# while using this as their module body
path: stylix/mk-target.nix
user: login: trueNAHO
body: Is this not already covered by:

https://github.com/nix-community/stylix/blob/bde3be9781e16ce164bfbd94be38fe21926a4d07/stylix/mk-target.nix#L11-L12
created_at: 2025-05-19T18:07:39Z
author_association: MEMBER
diff_hunk: @@ -60,5 +50,26 @@ in
             };
           };
         };
-      };
+      }
+    )
+    (
+      { fonts }:
+      with fonts;
path: modules/alacritty/hm.nix
user: login: MattSturgeon
body: Not sure this `with` is really needed anymore.

`monospace.name` isn't any better than `fonts.monospace.name` IMO, so it'd be nice to avoid having a `with`.
created_at: 2025-05-20T20:02:35Z
author_association: MEMBER
diff_hunk: @@ -1,26 +1,16 @@
 # Documentation is available at:
 # - https://alacritty.org/config-alacritty.html
 # - `man 5 alacritty`
-{ config, lib, ... }:
-
-let
-  colors = config.lib.stylix.colors.withHashtag;
-in
-{
-  options.stylix.targets.alacritty.enable =
-    config.lib.stylix.mkEnableTarget "Alacritty" true;
-
-  config =
-    lib.mkIf (config.stylix.enable && config.stylix.targets.alacritty.enable)
+{ config, mkTarget, ... }:
+mkTarget {
+  name = "alacritty";
+  humanName = "Alacritty";
+  configElements = [
+    (
+      { colors }:
+      with colors.withHashtag;
       {
         programs.alacritty.settings = {
-          font = with config.stylix.fonts; {
-            normal = {
-              family = monospace.name;
-              style = "Regular";
-            };
-            size = sizes.terminal;
-          };
           window.opacity = config.stylix.opacity.terminal;
path: modules/alacritty/hm.nix
user: login: 0xda157
body: shouldn't this use opacity from the configElements args?
created_at: 2025-05-21T15:09:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -60,5 +50,25 @@ in
             };
           };
         };
-      };
+      }
+    )
+    (
+      { fonts }:
+      {
+        programs.alacritty.settings.font = {
+          normal = {
+            family = fonts.monospace.name;
+            style = "Regular";
+          };
+          size = fonts.sizes.terminal;
+        };
+      }
+    )
+    (
+      { opacity }:
+      {
+        programs.alacritty.settings.window.opacity = config.stylix.opacity.terminal;
path: modules/alacritty/hm.nix
user: login: 0xda157
body: same here
created_at: 2025-05-21T15:09:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,26 +1,16 @@
 # Documentation is available at:
 # - https://alacritty.org/config-alacritty.html
 # - `man 5 alacritty`
-{ config, lib, ... }:
-
-let
-  colors = config.lib.stylix.colors.withHashtag;
-in
-{
-  options.stylix.targets.alacritty.enable =
-    config.lib.stylix.mkEnableTarget "Alacritty" true;
-
-  config =
-    lib.mkIf (config.stylix.enable && config.stylix.targets.alacritty.enable)
+{ config, mkTarget, ... }:
+mkTarget {
+  name = "alacritty";
+  humanName = "Alacritty";
+  configElements = [
+    (
+      { colors }:
+      with colors.withHashtag;
path: modules/alacritty/hm.nix
user: login: 0xda157
body: this double `with` ends up in colors without hashtags being used and violates best practices
created_at: 2025-05-21T16:20:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,26 +1,16 @@
 # Documentation is available at:
 # - https://alacritty.org/config-alacritty.html
 # - `man 5 alacritty`
-{ config, lib, ... }:
-
-let
-  colors = config.lib.stylix.colors.withHashtag;
-in
-{
-  options.stylix.targets.alacritty.enable =
-    config.lib.stylix.mkEnableTarget "Alacritty" true;
-
-  config =
-    lib.mkIf (config.stylix.enable && config.stylix.targets.alacritty.enable)
+{ config, mkTarget, ... }:
+mkTarget {
+  name = "alacritty";
+  humanName = "Alacritty";
+  configElements = [
+    (
+      { colors }:
+      with colors.withHashtag;
       {
         programs.alacritty.settings = {
-          font = with config.stylix.fonts; {
-            normal = {
-              family = monospace.name;
-              style = "Regular";
-            };
-            size = sizes.terminal;
-          };
           window.opacity = config.stylix.opacity.terminal;
path: modules/alacritty/hm.nix
user: login: Flameopathic
body: Yes, it should; good catch. I believe that I missed this in a rebase.
created_at: 2025-05-22T00:40:26Z
author_association: CONTRIBUTOR

PR comments:
user: login: Flameopathic
created_at: 2025-04-11T14:19:00Z
body: Infinite recursion... I'm not entirely sure how to debug this.
author_association: CONTRIBUTOR
user: login: Flameopathic
created_at: 2025-04-11T23:12:44Z
body: There are two things that I know to be causing infinite recursion in the current form of the function.

1. Distributing the function under `config.lib.stylix` and using it to set `config`
2. Checking `config.stylix.${arg} != null` in https://github.com/danth/stylix/blob/82586436b927c934fb1e308c6c7d1259e636c7e4/stylix/target.nix#L124 

For the former, I considered extending `lib` with it, but then it could no longer perform the latter check as it would no longer have access to `config`.
For the latter, I am not sure how to convince nixpkgs that we are not changing the top level Stylix options in the function.
author_association: CONTRIBUTOR
user: login: Flameopathic
created_at: 2025-04-20T16:48:52Z
body: > > * This is a `lib` function because distributing it through `config` causes infinite recursion
> >   
> >   * As a side effect, it also must be given `config` to function
>
> Does distributing it through an option work:
> 
> ```nix
> mkTarget = lib.mkOption {
>   # TODO: Declare other attributes.
> 
>   internal = true;
>   readOnly = true;
> };
> ```

I haven't documented this well, but I have tried several methods of distributing it, and none have worked as required just yet.
- Using `config.lib.stylix.mkTarget` results in infinite recursion
- Extending `lib` requires using an overlay which is set through `config` and thus results in infinite recursion
    - Extending `lib` through the flake allows `nix flake check` to pass, but it does not properly extend `lib` when any other system is inputting it. Presumably this is common knowledge, but this is the first time I have tried extending `lib` and thus am mentioning it in case it is either unexpected or I am doing it wrong.
- I had not tried making it a read-only option; however, it also seems to result in infinite recursion. For context, my implementation used `readOnly = true;` and `default = <<the function>>;`. I then called the function through `config.stylix.mkTarget`.

As I am writing this, the current implementation is to not distribute it at all and instead define it in the module (`hyprland`) that it is used for purposes of testing its functionality.
author_association: CONTRIBUTOR
user: login: MattSturgeon
created_at: 2025-05-04T06:37:28Z
body: The reason you're having issues with infinite recursion is because the function returns a module, which is presumably imported somewhere.

The fundamental issue here is you cannot use something from `config` to define modules or `imports`. That is because the full list of modules to import must be fully evaluated before `config` (or `options`) exist.

Historically, this is the one scenario where `specialArgs` had a valid use-case, as special args are fixed _outside_ of the module eval. This is why `lib` is a special arg and not a normal module arg, otherwise (e.g.) `mkRenamedOptionModule` wouldn't work.

However defining special args should truly be a last resort, as they have far more downsides than upsides.

Is there any way you can restructure your design so that you _don't_ need to produce an entire importable module? E.g. having a function to produce the option and another function to produce the config?
author_association: MEMBER
user: login: Flameopathic
created_at: 2025-05-04T15:01:09Z
body: Thank you for the detailed response! I am going to preface this comment with telling you that I only have a surface-level understanding of how the nixpkgs module system works and just want to learn more. With that out of the way, here we go.

---
> The reason you're having issues with infinite recursion is because the function returns a module, which is presumably imported somewhere.
> 
> The fundamental issue here is you cannot use something from `config` to define modules or `imports`. That is because the full list of modules to import must be fully evaluated before `config` (or `options`) exist.

Interesting.

 I was under the impression that the issue was that we are setting top-level `config` attributes using something from `config` which nixpkgs could not be sure we weren't declaring multiple times. This impression doesn't really make sense though as using a `readOnly` option still resulted in infinite recursion. I accept your explanation.

---
> Historically, this is the one scenario where `specialArgs` had a valid use-case, as special args are fixed _outside_ of the module eval. This is why `lib` is a special arg and not a normal module arg, otherwise (e.g.) `mkRenamedOptionModule` wouldn't work.

That tracks. Is this also why `lib.mkIf` works despite being able to alter `config` using values from `config`?

---
> However defining special args should truly be a last resort, as they have far more downsides than upsides.

Would you explain
1. what the downsides (or at least the biggest downside) of `specialArgs` is
2. if there is even a way to use `specialArgs` outside of when defining nixos systems (e.g. using it in the context of Stylix)

---
> Is there any way you can restructure your design so that you _don't_ need to produce an entire importable module? E.g. having a function to produce the option and another function to produce the config?

In the current state of the function, this would be trivial. In the future, though, one of the features of this function is planned to be generating per-target `colors`, `image`, `fonts`, etc. options based on the attributes that the target uses in `configElements` and `generalConfig`. I don't believe this would be possible if the function were split into two, and this is one of the main draws of the function.

Also, if my original impression of why the infinite-recursion was occurring holds any water (which it may hold none at all), would using a function set under `config` to set `config` not cause problems? I want to say that 
```nix
{ config, ... }:
{
  config = config.something;
}
```
is all that you need for infinite recursion, but again, I am willing to accept if I am wrong.
author_association: CONTRIBUTOR
user: login: MattSturgeon
created_at: 2025-05-04T16:09:47Z
body: > Thank you for the detailed response! I am going to preface this comment with telling you that I only have a surface-level understanding of how the nixpkgs module system works and just want to learn more.

I'll do my best to help out, feel free to reach out on matrix if you have questions that feel of topic or too noisy for the PR. `@mattsturg:matrix.org`

> I was under the impression that the issue was that we are setting top-level `config` attributes using something from `config` which nixpkgs could not be sure we weren't declaring multiple times. This impression doesn't really make sense though as using a `readOnly` option still resulted in infinite recursion. I accept your explanation.

There's a little nuance here I'll try to summarize. You're right that there is more than one way to get infinite recursion. Using a function defined as part of `options` and `config` to create/define modules to be imported is the most obvious example here.

Another example that you may encounter even outside of the module system is using the values of an attrset to recursively define the _shape_ of that same attrset. By shape, I essentially mean the attr names.

<details><summary><strong>Examples</strong></summary>
<p>

E.g. this is infinitely recursive:

```nix
{
  lib ? import <nixpkgs/lib>,
}:
let
  self =
    {
      names = [ "bob" "fred" "mary" "zoe" ];
    }
    // lib.genAttrs self.names (name: "hi ${name}");
in
self
```

It is recursive because `self` is partially defined based on something in `self`. You might expect it to evaluate to:

```nix
{
  names = [ "bob" "fred" "mary" "zoe" ];
  bob = "hi bob";
  fred = "hi fred";
  mary = "hi mary";
  zoe = "hi zoe";
}
```

However because the _shape_ of `self` depends on `self.names` (an attr of `self`) it is infinitely recursive.

As a counter example, this is recursive, but not infinitely. In this case, because the keys are statically fixed:


```nix
let
  inherit (builtins) elemAt;

  self = {
    names = [ "bob" "fred" "mary" "zoe" ];
    bob = "hi ${elemAt self.names 0}";
    fred = "hi ${elemAt self.names 1}";
    mary = "hi ${elemAt self.names 2}";
    zoe = "hi ${elemAt self.names 3}";
  };
in
self
```

Another example where an attrset refers to itself infinitely, but because nix evaluates lazily it is not an issue:

```nix
let
  inherit (builtins) elemAt;

  self = {
    names = [ "bob" "fred" "mary" "zoe" ];
    inherit self;
  };
in
self
```

Here you could evaluate `self.self.self.self` indefinitely, but nix will not complain unless you try to evaluate "strictly"; (in nix terminology, "strict" is the opposite of "lazy").


</p>
</details> 

> > Historically, this is the one scenario where `specialArgs` had a valid use-case, as special args are fixed _outside_ of the module eval. This is why `lib` is a special arg and not a normal module arg, otherwise (e.g.) `mkRenamedOptionModule` wouldn't work.
> 
> That tracks. Is this also why `lib.mkIf` works despite being able to alter `config` using values from `config`?

Not quite. `mkIf` _can_ help to avoid some forms of infinite recursion. It's not related to importing modules though and its benefits have nothing to do with `lib` being a special arg IIUC.

> > However defining special args should truly be a last resort, as they have far more downsides than upsides.
> 
> Would you explain
> 
>     1. what the downsides (or at least the biggest downside) of `specialArgs` is
> 
>     2. if there is even a way to use `specialArgs` outside of when defining nixos systems (e.g. using it in the context of Stylix)

No, stylix can't really define special args because they must be passed form outside the configureation, and stylix is just a module that lives within the configuration. If stylix was evaluating its own configuration it could specify extra special args; e.g. home-manager adds things like `osConfig` and overrides the `lib` special arg, IIRC.

This inability to define special args from a module is one of the main downsides of special args, although the fact they're passed in from outside the eval is what makes them _special_.

This means modules cannot (for example) modify or extend `lib`, and instead have to resort to using a confusingly named `lib` option (aka `config.lib`).

In contrast, other non-special module args (like `pkgs`) _can_ be defined or modified from within the module eval. In fact both NixOS and home-manager have `config.nixpkgs.*` options for doing exactly that; this is why you can use `config.nixpkgs.overlays` to modify the `pkgs` arg, for example.

The other major downside is that relying on any module arg, but especially special args, makes your module less portable. I.e. you can only use your module in configurations that have the args you use.

> > Is there any way you can restructure your design so that you _don't_ need to produce an entire importable module? E.g. having a function to produce the option and another function to produce the config?
> 
> In the current state of the function, this would be trivial. In the future, though, one of the features of this function is planned to be generating per-target `colors`, `image`, `fonts`, etc. options based on the attributes that the target uses in `configElements` and `generalConfig`. I don't believe this would be possible if the function were split into two, and this is one of the main draws of the function.

An alternative to redesigning the function is to avoid storing it in a module option's value.

For example, if it is only used in one place, it could be defined locally in a `let in` block.

Or if it is used in a few places, it could be defined in an `import`-able file and consumed via `import ../../lib/mk_target.nix { /* args */ }`.

Or if the modules that use it are imported from a place that has access to the function, it could be passed to them as an additional argument (unrelated to the module system):

```nix
{
  imports = [
    # NOTE: for unrelated reasons, `lib.importApply` would be better than `import` here.
    # Functionally, `import` and `importApply` are almost identical though.
    (import ./some-module.nix { inherit mkTarget; })
  ];
}
```


> Also, if my original impression of why the infinite-recursion was occurring holds any water (which it may hold none at all), would using a function set under `config` to set `config` not cause problems? I want to say that
> 
> ```nix
> { config, ... }:
> {
>   config = config.something;
> }
> ```
> 
> is all that you need for infinite recursion, but again, I am willing to accept if I am wrong.

Most of the time you _can_ use values in `config` to define other values in `config`. Sometimes it ends up being inf-rec, but _most_ of the time laziness prevents that. If this wasn't the case, then 90% of option definitions wouldn't be possible. Almost all conditional definitions are using the value of some config option as part of their condition, the most obvious example being `enable` options.

What _can_ fall into the trap of inf-rec fairly easily is if you want to decide which options to _declare_ based on some other config value. This is a fancier example of the _"attrset's shape depends on its own values"_ problem I described above.

<details><summary><strong>Example</strong></summary>
<p>

For example, this is inf-rec because the names of the options being defined in the `foo.bar` attrset are defined by a value also within the `foo.bar` attrset:

```nix
# module 1: declares and defines foo.bar.names
{ lib, ... }:
{
  options.foo.bar.names = lib.mkOption {
    type = lib.types.listOf lib.types.str;
    description = "A list of names for the other `foo.bar.*` options to declare.";
    default = [ ];
  };

  config.foo.bar.names = [
    "Isabella"
    "Tom"
    "Mark"
    "Lucy"
  ];
}
```

```nix
# module 2: declares options based on foo.bar.names
{ lib, config, ... }:
{
  options.foo.bar = lib.genAttrs config.foo.bar.names (
    name:
    lib.mkOption {
      type = lib.types.anything;
      description = "An option representing ${name}.";
      default = name;
    }
  );
}
```

This is inf-rec for the same reason outlined in the other examples above.

</p>
</details> 

---

Apologies for the wall of text, or if any part ended up too rambly.
author_association: MEMBER
user: login: Flameopathic
created_at: 2025-05-04T18:15:36Z
body: > Apologies for the wall of text, or if any part ended up too rambly.

Amazing answer to all of my questions, thank you for taking the time to make this.

---
Current solution turns function into a file which can be called through `lib.modules.importApply`. Using `import` alone gives the following error:
```
error: module /nix/store/2wvzzjddj17vhavz6875fd206ga9n04w-source/modules/hyprland/hm.nix (/nix/store/2wvzzjddj17vhavz6875fd206ga9n04w-source/modules/hyprland/hm.nix) does not look like a module.
```

I do not know whether this breaks the use case outlined in https://stylix.danth.me/tricks.html?highlight=disabledM#completely-disabling-some-stylix-targets
author_association: CONTRIBUTOR
user: login: MattSturgeon
created_at: 2025-05-04T18:25:39Z
body: > Using `import` alone gives the following error:
> 
> ```
> error: module /nix/store/2wvzzjddj17vhavz6875fd206ga9n04w-source/modules/hyprland/hm.nix (/nix/store/2wvzzjddj17vhavz6875fd206ga9n04w-source/modules/hyprland/hm.nix) does not look like a module.
> ```

Using `import` or `importApply` should have very little difference. The technical differences are:

1. `importApply` assumes the file contains a function that has exactly **one** arg, then returns a module (or module function)
2. `import` can be used with an arbitrary number of args before returning a module, but you must apply the right number of args when importing
3. `import`-ing a module will inherit the `_file` location from the importer
4. importing with `importApply` will set the `_file` location to the imported file


Regarding the `_file` location, this is the location that shows up in errors and docs relating to option declarations and definitions.

Usually when importing a module you _would_ want to set the location to that module (this happens automatically when adding a path-literal to the `imports` list). However when using a file to _generate_ a module it's debatable whether you want the location to be the file that is _using_ the function or the file where the function lives.

For example, `lib.mkRenamedOptionModule` does **not** set `_file = "${nixpkgs}/lib/modules.nix"`, because it makes more sense to inherit the location from the module that _used_ `lib.mkRenamedOptionModule`.
author_association: MEMBER
user: login: Flameopathic
created_at: 2025-05-04T18:39:08Z
body: > > Using `import` alone gives the following error:
> > ```
> > error: module /nix/store/2wvzzjddj17vhavz6875fd206ga9n04w-source/modules/hyprland/hm.nix (/nix/store/2wvzzjddj17vhavz6875fd206ga9n04w-source/modules/hyprland/hm.nix) does not look like a module.
> > ```
> 
> Using `import` or `importApply` should have very little difference. The technical differences are:
> 
>     1. `importApply` assumes the file contains a function that has exactly **one** arg, then returns a module (or module function)
> 
>     2. `import` can be used with an arbitrary number of args before returning a module, but you must apply the right number of args when importing
>
>     3. `import`-ing a module will inherit the `_file` location from the importer
> 
>     4. importing with `importApply` will set the `_file` location to the imported file
> 
> 
> Regarding the `_file` location, this is the location that shows up in errors and docs relating to option declarations and definitions.
> 
> Usually when importing a module you _would_ want to set the location to that module (this happens automatically when adding a path-literal to the `imports` list). However when using a file to _generate_ a module it's debatable whether you want the location to be the file that is _using_ the function or the file where the function lives.

In this case, I think we would want errors to be shown as coming from the importing module rather than the imported; however, using `import ../../stylix/mk-target.nix { /* args */ }` gives the error while `lib.modules.importApply ../../stylix/mk-target.nix { /* args */ }` does not. I am giving the `mk-target` module function one argument while it requests requests two arguments, but I expect that the second (`{ config, lib, ...}`) will be filled by the module system.
author_association: CONTRIBUTOR
user: login: trueNAHO
created_at: 2025-05-12T20:31:24Z
body: For reference, I am currently reviewing this patchset and should be able to send extensive feedback and suggestions tomorrow.
author_association: MEMBER
user: login: Flameopathic
created_at: 2025-05-13T19:34:47Z
body: I second thanking @MattSturgeon for the reviewing (and like 25% authoring) of this PR. This was a much larger undertaking than I was expecting, and with the help I received, I feel very good about the outcome and have learned *a lot* about Nix and best practices.

I have implemented all requested changes, as well as adding an extra (revertible or squashable) commit which passes `mkTarget` as a module arg as described in https://github.com/danth/stylix/pull/1130#discussion_r2087239888.

I believe that it would be best to merge this PR as three commits, one for creation of the function and the other two for application in the two modules that this PR changed, but I think that one or two commits would also be fairly readable.
author_association: CONTRIBUTOR
user: login: trueNAHO
created_at: 2025-05-13T20:21:14Z
body: > adding an extra (revertible or squashable) commit which passes `mkTarget` as a module arg as described in [#1130 (comment)](https://github.com/danth/stylix/pull/1130#discussion_r2087239888).

See an alternative proposal in https://github.com/danth/stylix/pull/1130#discussion_r2087539159.

> I believe that it would be best to merge this PR as three commits, one for creation of the function and the other two for application in the two modules that this PR changed, but I think that one or two commits would also be fairly readable.

Should we apply the `mkTarget` function to all `/modules/<MODULE>/<PLATFORM>.nix` targets, resulting in the following commit series:

> - `stylix: add mkTarget function`
> - `treewide: apply mkTarget function`
>
> -- https://github.com/danth/stylix/pull/1130#pullrequestreview-2837078061

Applying `mkTarget` to all targets may reveal edge cases that are hard to predict, requiring reconsideration of the design of the `mkTarget` function. I suppose the `/modules/{discord,firefox}/hm.nix` targets uncover neat edge cases, or at least elegant `mkTarget` applications. However, I suggest applying this treewide change once `mkTarget` is considered "done", which is probably just resolving https://github.com/danth/stylix/pull/1130#discussion_r2087539159 at this point.

Although the GitHub UI fancily renders code formatting in commit messages, the plain text in `git log` reads better without them:

```diff
-stylix: add `mkTarget` function
-alacritty: use `mkTarget`
-hyprland: use `mkTarget`
+stylix: add mkTarget function
+alacritty: use mkTarget
+hyprland: use mkTarget
```

author_association: MEMBER
user: login: MattSturgeon
created_at: 2025-05-13T20:26:55Z
body: > > adding an extra (revertible or squashable) commit which passes `mkTarget` as a module arg as described in https://github.com/danth/stylix/pull/1130#discussion_r2087239888.
> 
> See an alternative proposal in https://github.com/danth/stylix/pull/1130#discussion_r2087539159.

And _another_ alternative in https://github.com/danth/stylix/pull/1130#discussion_r2087566335

> Should we apply the `mkTarget` function to all `/modules/<MODULE>/<PLATFORM>.nix` targets, resulting in the following commit series:

I believe there are still some modules that `mkTarget` doesn't support. I'd prefer keeping this PR small and expanding `mkTarget` and its usage over time.

> However, I suggest applying this treewide change once `mkTarget` is considered "done"

:+1: 

> Although the GitHub UI fancily renders code formatting in commit messages, the plain text in `git log` reads better without them:

Personally I disagree. Markdown is intended to be just as readable unrendered as it is rendered.

Seeing `` `backticks` `` in a commit message highlights that it is code just as much as seeing a `code element` on github.

While there's personal preference, I tend to use a _small_ amount of markdown in commit messages.
author_association: MEMBER
user: login: trueNAHO
created_at: 2025-05-13T20:39:09Z
body: > > Should we apply the `mkTarget` function to all `/modules/<MODULE>/<PLATFORM>.nix` targets, resulting in the following commit series:
>
> I believe there are still some modules that `mkTarget` doesn't support. I'd prefer keeping this PR small and expanding `mkTarget` and its usage over time.

Sounds good. Maybe we can still apply the `mkTarget` function to all targets where doing so it "trivial".

> > Although the GitHub UI fancily renders code formatting in commit messages, the plain text in `git log` reads better without them:
>
> Personally I disagree. Markdown is intended to be just as readable unrendered as it is rendered.
>
> Seeing `` `backticks` `` in a commit message highlights that it is code just as much as seeing a `code element` on github.
>
> While there's personal preference, I tend to use a _small_ amount of markdown in commit messages.

I picked this up from reading Linux Kernel commit messages and their plain text documentation and code comments. They (almost always) treat code as regular text, unless grouping is non-obvious as in:

- ```
  Declare options with the lib.mkEnableTarget function.
  ```

- ```
  Declare options with a 'enable = lib.mkEnableTarget "Example Target" true;' declaration.
  ```

- ```
  Declare options with a

      enable = lib.mkEnableTarget "Example Target" true;

  declaration.
  ```

author_association: MEMBER
user: login: Flameopathic
created_at: 2025-05-15T23:23:41Z
body: I think that separating targets entirely from the module system has little benefit and makes this method of configuration even more confusing for new contributors than this function already will make it.

I think that distributing the function with module args is the most intuitive way to do it and allows for more creative use of the function than forcing every target to be configured as a simple attribute set. `mkTarget` as it stands may even be able to be used in a map in modules like the Firefox module.

@trueNAHO do you accept distributing `mkTarget` with the module args? If so, I will prettify the commits.

I don't know if the treewide reformat should be done in this PR; it will take too long for just me to get all 87 modules reformatted to use `mkTarget`. I think it would be better done in later PRs.
author_association: CONTRIBUTOR
user: login: MattSturgeon
created_at: 2025-05-15T23:51:20Z
body: > I think that distributing the function **as a module arg** is the most intuitive way 

Just to nit pick on terminology:

It technically still isn't a module arg. I.e. a module `{ mkTarget, ... }: { }` it'd panic trying to find the arg if imported through _normal_ means (without `autoload.nix`).

It's hard to phrase this accurately without being confusing; but rather than being distributed **as** a module arg, it's more like it's being distributed **with** the module args, i.e. alongside them.

More specifically, autoload is acting as a "man in the middle"; applying the module-function's args itself so that it can also apply **non-module** args with them, without using the usual methods of `_module.args` and `specialArgs`.
author_association: MEMBER
user: login: stylix-automation[bot]
created_at: 2025-05-21T14:22:01Z
body: Successfully created backport PR for `release-25.05`:
- #1331
author_association: CONTRIBUTOR
user: login: Flameopathic
created_at: 2025-05-21T14:26:37Z
body: Thank you @awwpotato, @trueNAHO, and @MattSturgeon :heart: 
author_association: CONTRIBUTOR
user: login: Lyndeno
created_at: 2025-05-22T06:14:13Z
body: This seems to unconditionally enable hyperpaper on my machine even though I do not have hyprland enabled.
author_association: CONTRIBUTOR
user: login: Eveeifyeve
created_at: 2025-05-22T06:58:30Z
body: > This seems to unconditionally enable hyperpaper on my machine even though I do not have hyprland enabled.

Please open up a separate issue than commenting here please, because a lot of people are following this pull request.
author_association: MEMBER