diff --git a/home/desktop/cosmic.nix b/home/desktop/cosmic.nix
new file mode 100644
index 0000000..3337b21
--- /dev/null
+++ b/home/desktop/cosmic.nix
@@ -0,0 +1,32 @@
+_:
+
+{
+  imports = [ ../modules/cosmic ];
+
+  programs.cosmic = {
+    enable = true;
+    settings = {
+      "com.system76.CosmicTk".v1 = {
+        icon_theme = "Cosmic";
+      };
+      "com.system76.CosmicFiles".v1 = {
+        tab.show_hidden = true;
+        favorites = [
+          "Home"
+          "Documents"
+          "Downloads"
+          "Music"
+          "Pictures"
+          "Videos"
+          ''Path("/home/felschr/dev")''
+          ''Path("/home/felschr/dev/work")''
+          ''Path("/home/felschr/dev/mscs")''
+          ''Path("/home/felschr/Documents/CU Boulder MSCS")''
+        ];
+      };
+      "com.system76.CosmicAppletAudio".v1 = {
+        show_media_controls_in_top_panel = true;
+      };
+    };
+  };
+}
diff --git a/home/desktop/default.nix b/home/desktop/default.nix
index b5d0adb..ee28be4 100644
--- a/home/desktop/default.nix
+++ b/home/desktop/default.nix
@@ -4,6 +4,7 @@
   imports = [
     ./gtk.nix
     ./gnome.nix
+    ./cosmic.nix
     ./mimeapps.nix
   ];
 }
diff --git a/home/flake-module.nix b/home/flake-module.nix
index caee67c..2849f00 100644
--- a/home/flake-module.nix
+++ b/home/flake-module.nix
@@ -33,6 +33,7 @@ in
       firefox = import ./modules/firefox/firefox.nix;
       tor-browser = import ./modules/firefox/tor-browser.nix;
       mullvad-browser = import ./modules/firefox/mullvad-browser.nix;
+      cosmic = import ./modules/cosmic;
 
       # users
       felschr = import ./felschr.nix;
diff --git a/home/modules/cosmic/default.nix b/home/modules/cosmic/default.nix
new file mode 100644
index 0000000..7a5cc08
--- /dev/null
+++ b/home/modules/cosmic/default.nix
@@ -0,0 +1,53 @@
+{ config, lib, ... }:
+
+# Based on:
+# https://github.com/tristanbeedell/home-manager/tree/efa4d272f6c2b14d4a3b67b0b1e4b38ae46e5588/modules/programs/cosmic
+
+let
+  cfg = config.programs.cosmic;
+
+  ron = import ./ron.nix { inherit lib; };
+in
+{
+  imports = [ ./settings.nix ];
+
+  options.programs.cosmic = {
+    enable = lib.mkEnableOption "COSMIC Desktop";
+    settings = lib.mkOption {
+      default = { };
+      type = lib.types.submodule {
+        freeformType = lib.types.submodule {
+          freeformType = lib.types.attrsOf lib.types.anything;
+        };
+      };
+      description = ''
+        An attrset of explicit settings for COSMIC apps, using their full config path.
+      '';
+      example = lib.literalExpression ''
+        {
+          "com.system76.CosmicPanel.Dock".v1 = {
+            opacity = 0.8;
+          };
+        };
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    xdg.configFile = lib.concatMapAttrs (
+      component:
+      lib.concatMapAttrs (
+        version:
+        lib.concatMapAttrs (
+          option: value:
+          lib.optionalAttrs (value != null) {
+            "cosmic/${component}/${version}/${option}" = {
+              enable = true;
+              text = ron.serialise value;
+            };
+          }
+        )
+      )
+    ) cfg.settings;
+  };
+}
diff --git a/home/modules/cosmic/ron.nix b/home/modules/cosmic/ron.nix
new file mode 100644
index 0000000..3727ea9
--- /dev/null
+++ b/home/modules/cosmic/ron.nix
@@ -0,0 +1,129 @@
+{ lib }:
+let
+  inherit (lib)
+    filterAttrs
+    concatStrings
+    concatStringsSep
+    mapAttrsToList
+    concatLists
+    foldlAttrs
+    boolToString
+    ;
+  inherit (builtins) typeOf toString stringLength;
+
+  # list -> array
+  array = a: "[${concatStringsSep "," (map serialise a)}]";
+
+  # attrset -> hashmap
+  _assoc = a: mapAttrsToList (name: val: "${name}: ${val},") a;
+  assoc = a: ''
+    {
+        ${concatStringsSep "\n    " (concatLists (map _assoc a))}
+    }
+  '';
+
+  stringArray = a: array (map toQuotedString a);
+
+  tuple = a: "(${concatStringsSep "," (map serialise a)})";
+  enum =
+    s:
+    if isNull s.value then
+      s.name
+    else
+      concatStrings [
+        s.name
+        "("
+        (serialise s.value)
+        ")"
+      ];
+
+  option =
+    value:
+    if isNull value then
+      "None"
+    else
+      enum {
+        name = "Some";
+        inherit value;
+      };
+
+  # attrset -> struct
+  _struct_kv =
+    k: v:
+    if v == null then
+      ""
+    else
+      (concatStringsSep ": " [
+        k
+        (serialise v)
+      ]);
+  _struct_concat =
+    s:
+    foldlAttrs (
+      acc: k: v:
+      if stringLength acc > 0 then
+        concatStringsSep ", " [
+          acc
+          (_struct_kv k v)
+        ]
+      else
+        _struct_kv k v
+    ) "" s;
+  _struct_filt = s: _struct_concat (filterAttrs (k: v: v != null) s);
+  struct = s: "(${_struct_filt s})";
+
+  toQuotedString = s: ''"${toString s}"'';
+
+  # this is an enum, but use string interp to make sure it's put in the nix store
+  path = p: ''Path("${p}")'';
+
+  # attrset for best-effort serialisation of nix types
+  # currently, no way to differentiate between enums and structs
+  serialisers = {
+    int = toString;
+    float = toString;
+    bool = boolToString;
+    # can't assume quoted string, sometimes it's a Rust enum
+    string = toString;
+    path = path;
+    null = _: "None";
+    set = struct;
+    list = array;
+  };
+
+  serialise = v: serialisers.${typeOf v} v;
+
+in
+{
+  inherit
+    array
+    assoc
+    tuple
+    stringArray
+    serialise
+    option
+    path
+    struct
+    toQuotedString
+    enum
+    ;
+
+  # some handy wrapper types, to reduce transformation effort in modules producing Ron
+  types = {
+    # string type, but implicitly wraps in quote marks for usage in Ron
+    str = (lib.types.coercedTo lib.types.str toQuotedString lib.types.str) // {
+      description = "string";
+    };
+
+    # takes a (non submodule) type, and implicitly wraps in Rust Option<> type
+    option =
+      type:
+      let
+        wantedType = lib.types.nullOr (type);
+      in
+      (lib.types.coercedTo (wantedType) (option) lib.types.str)
+      // {
+        description = wantedType.description;
+      };
+  };
+}
diff --git a/home/modules/cosmic/settings.nix b/home/modules/cosmic/settings.nix
new file mode 100644
index 0000000..8a8a101
--- /dev/null
+++ b/home/modules/cosmic/settings.nix
@@ -0,0 +1,108 @@
+{ lib, ... }:
+
+# Known definitions for settings options
+
+let
+  inherit (lib) types;
+  ron = import ./ron.nix { inherit lib; };
+  cosmic = {
+    types = import ./types.nix { inherit lib ron; };
+  };
+in
+{
+  options.programs.cosmic.settings = {
+    # https://github.com/pop-os/libcosmic/blob/1fce5df160f595d1b1e5a8e2bb2a24775419f82d/src/config/mod.rs#L85
+    "com.system76.CosmicTk".v1 = {
+      apply_theme_global = lib.mkOption {
+        type = types.nullOr types.bool;
+        default = null;
+        description = "Apply the theme to other toolkits.";
+      };
+      show_minimize = lib.mkOption {
+        type = types.nullOr types.bool;
+        default = null;
+        description = "Show minimize button in window header.";
+      };
+      show_maximize = lib.mkOption {
+        type = types.nullOr types.bool;
+        default = null;
+        description = "Show maximize button in window header.";
+      };
+      icon_theme = lib.mkOption {
+        type = types.nullOr ron.types.str;
+        default = null;
+        description = "Preferred icon theme.";
+      };
+      header_size = lib.mkOption {
+        type = types.nullOr cosmic.types.density;
+        default = null;
+        description = "Density of CSD/SSD header bars.";
+      };
+      interface_density = lib.mkOption {
+        type = types.nullOr cosmic.types.density;
+        default = null;
+        description = "Interface density.";
+      };
+      interface_font = lib.mkOption {
+        type = types.nullOr cosmic.types.font_config;
+        default = null;
+        description = "Interface font family";
+      };
+      monospace_font = lib.mkOption {
+        type = types.nullOr cosmic.types.font_config;
+        default = null;
+        description = "Mono font family";
+      };
+    };
+
+    # https://github.com/pop-os/cosmic-files/blob/1a5a4501ee501b3155295cbfccc1c992b5ec9c01/src/config.rs#L106
+    "com.system76.CosmicFiles".v1 = {
+      app_theme = lib.mkOption {
+        type = types.nullOr cosmic.types.theme;
+        default = null;
+        description = "";
+      };
+      desktop = lib.mkOption {
+        type = types.nullOr cosmic.types.files_desktop;
+        default = null;
+        description = "";
+      };
+      favorites = lib.mkOption {
+        type = types.nullOr cosmic.types.files_favorites;
+        default = null;
+        description = "";
+      };
+      show_details = lib.mkOption {
+        type = types.nullOr types.bool;
+        default = null;
+        description = "";
+      };
+      tab = lib.mkOption {
+        type = types.nullOr cosmic.types.files_tab_config;
+        default = null;
+        description = "";
+      };
+      type_to_search = lib.mkOption {
+        type = types.nullOr (
+          types.enum [
+            "Recursive"
+            "EnterPath"
+          ]
+        );
+        default = null;
+        description = "";
+      };
+    };
+
+    # https://github.com/pop-os/cosmic-applets/blob/b4b465712218be8d26b6772a382d19f4c3ff97f8/cosmic-applet-audio/src/config.rs#L9
+    "com.system76.CosmicAppletAudio".v1 = {
+      show_media_controls_in_top_panel = lib.mkOption {
+        type = types.nullOr types.bool;
+        default = null;
+        description = "";
+      };
+    };
+
+    # ...
+  };
+}
diff --git a/home/modules/cosmic/types.nix b/home/modules/cosmic/types.nix
new file mode 100644
index 0000000..9ad749a
--- /dev/null
+++ b/home/modules/cosmic/types.nix
@@ -0,0 +1,145 @@
+{ lib, ron }:
+
+let
+  inherit (lib) types;
+in
+lib.fix (self: {
+  theme = types.enum [
+    "Dark"
+    "Light"
+    "System"
+  ];
+  density = types.enum [
+    "Compact"
+    "Spacious"
+    "Standard"
+  ];
+  weight = types.enum [
+    "Thin"
+    "ExtraLight"
+    "Light"
+    "Normal"
+    "Medium"
+    "Semibold"
+    "Bold"
+    "ExtraBold"
+    "Black"
+  ];
+  stretch = types.enum [
+    "UltraCondensed"
+    "ExtraCondensed"
+    "Condensed"
+    "SemiCondensed"
+    "Normal"
+    "SemiExpanded"
+    "Expanded"
+    "ExtraExpanded"
+    "UltraExpanded"
+  ];
+  style = types.enum [
+    "Normal"
+    "Italic"
+    "Oblique"
+  ];
+
+  # libcosmic
+  font_config = types.submodule {
+    options = {
+      family = lib.mkOption {
+        type = ron.types.str;
+        default = null;
+      };
+      weight = lib.mkOption {
+        type = types.nullOr self.weight;
+        default = null;
+      };
+      stretch = lib.mkOption {
+        type = types.nullOr self.stretch;
+        default = null;
+      };
+      style = lib.mkOption {
+        type = types.nullOr self.style;
+        default = null;
+      };
+    };
+  };
+
+  # Files
+  files_desktop = types.submodule {
+    options = {
+      grid_spacing = lib.mkOption {
+        type = types.nullOr types.int;
+        default = null;
+      };
+      icon_size = lib.mkOption {
+        type = types.nullOr types.int;
+        default = null;
+      };
+      show_content = lib.mkOption {
+        type = types.nullOr types.bool;
+        default = null;
+      };
+      show_mounted_drives = lib.mkOption {
+        type = types.nullOr types.bool;
+        default = null;
+      };
+      show_trash = lib.mkOption {
+        type = types.nullOr types.bool;
+        default = null;
+      };
+    };
+  };
+  files_favorites = types.listOf (
+    types.oneOf [
+      (types.enum [
+        "Home"
+        "Documents"
+        "Downloads"
+        "Music"
+        "Pictures"
+        "Videos"
+      ])
+      (lib.types.strMatching ''^Path\(".*"\)$'')
+    ]
+  );
+  files_view = types.enum [
+    "Grid"
+    "List"
+  ];
+  files_icon_sizes = types.submodule {
+    options = {
+      list = lib.mkOption {
+        type = types.nullOr types.int;
+        default = null;
+      };
+      grid = lib.mkOption {
+        type = types.nullOr types.int;
+        default = null;
+      };
+    };
+  };
+  files_tab_config = types.submodule {
+    options = {
+      view = lib.mkOption {
+        type = types.nullOr self.files_view;
+        default = null;
+      };
+      folders_first = lib.mkOption {
+        type = types.nullOr types.bool;
+        default = null;
+      };
+      show_hidden = lib.mkOption {
+        type = types.nullOr types.bool;
+        default = null;
+      };
+      icon_sizes = lib.mkOption {
+        type = types.nullOr self.files_icon_sizes;
+        default = null;
+      };
+      single_click = lib.mkOption {
+        type = types.nullOr types.bool;
+        default = null;
+      };
+    };
+  };
+})