GTK4/libadwaita apps (Ghostty, Trayscale, and others) show grey placeholder icons instead of proper symbolic icons (tab-new-symbolic, window-close-symbolic, open-menu-symbolic, etc.) on KDE Plasma 6 running on NixOS.
This is a three-layer problem: a portal architecture mismatch, a dconf overwrite race, and NixOS path fragmentation — all stacked on top of each other.

The Root Cause

Layer 1: GTK4 can't hear KDE's portal

GTK4 on Wayland queries the xdg-desktop-portal for settings using these patterns:
org.gnome.desktop.interface / icon-theme org.freedesktop.appearance / *
KDE's portal (xdg-desktop-portal-kde) exposes the icon theme ONLY as:
org.kde.kdeglobals.Icons / Theme
GTK4 asks for org.gnome.*. KDE answers with org.kde.*. Neither side translates. When GTK4 gets no answer, it falls back to hicolor — which has no symbolic icons.
Confirmed via GTK_DEBUG=icontheme ghostty — the debug output shows GTK4 scanning ONLY hicolor directories on the filesystem, never Adwaita or Breeze.

Layer 2: KDE overwrites dconf on every login

KDE's kde-gtk-config daemon reads ~/.config/kdeglobals and writes the icon theme to dconf (org.gnome.desktop.interface icon-theme = "breeze") on every login. Even if you set dconf to "Adwaita" via gsettings or home-manager's dconf.settings, KDE overwrites it at session start.

Layer 3: NixOS icon path fragmentation

On NixOS, icon themes live in isolated Nix store paths. GTK4 finds icons via XDG_DATA_DIRS. The Adwaita icon theme must be both installed AND reachable via XDG_DATA_DIRS — just installing the package isn't enough if its store path isn't in the search path.

What We Tried (and why it failed)

Setting gtk.iconTheme in home-manager

gtk = { enable = true; iconTheme = { package = pkgs.adwaita-icon-theme; name = "Adwaita"; }; };
Writes gtk-icon-theme-name=Adwaita to ~/.config/gtk-3.0/settings.ini and ~/.config/gtk-4.0/settings.ini. GTK4 on Wayland ignores settings.ini for icon-theme — it reads from the portal/dconf instead.

Setting dconf via home-manager

dconf.settings = { "org/gnome/desktop/interface" = { icon-theme = "Adwaita"; }; };
Sets dconf correctly on activation, but kde-gtk-config overwrites it with "breeze" on every login, after home-manager has already run.

Patching Breeze's index.theme to inherit Adwaita

Changed Inherits=hicolor to Inherits=hicolor,Adwaita. Didn't work because GTK4 never even looks at Breeze — it falls back to hicolor directly since the portal returns nothing GTK4 understands.

gsettings set from a terminal

gsettings set org.gnome.desktop.interface icon-theme 'Adwaita'
Sets dconf, but with the KDE portal still active and no GTK portal fallback, GTK4 reads from the portal (which says nothing) rather than dconf.

System-level xdg.portal.config.common

xdg.portal.config.common = { "org.freedesktop.impl.portal.Settings" = [ "kde" "gtk" ]; };
Doesn't generate a portals.conf file at either system or user level on NixOS. The option exists but produces no output. Confirmed by checking both /etc/xdg-desktop-portal/ and ~/.config/xdg-desktop-portal/ — empty.

home-manager portals.conf (reverted prematurely)

We had this working but reverted it in favor of the system-level approach (which turned out to be a dead end). The user-level portals.conf IS the correct approach.

The Fix (what actually works)

Three pieces, all required:

1. Install Adwaita and configure GTK (home-manager)

# home/base/programs/gtk.nix { pkgs, ... }: { gtk = { enable = true; iconTheme = { package = pkgs.adwaita-icon-theme; name = "Adwaita"; }; }; dconf.settings = { "org/gnome/desktop/interface" = { icon-theme = "Adwaita"; }; }; }
This ensures Adwaita is installed, settings.ini is written (for GTK3), and dconf has the correct value (for the GTK portal to serve).

2. User-level portals.conf — GTK portal as Settings fallback

# In the same gtk.nix xdg.configFile."xdg-desktop-portal/portals.conf".text = '' [preferred] default=kde org.freedesktop.impl.portal.Settings=gtk;kde '';
This tells xdg-desktop-portal: KDE portal handles everything by default (file chooser, etc.), but for the Settings interface, try GTK portal first, then KDE. The GTK portal reads dconf, where we've set icon-theme = "Adwaita". KDE portal handles org.freedesktop.appearance (color-scheme, accent).
You must also have xdg-desktop-portal-gtk installed:
# modules/desktop/default.nix (or equivalent) xdg.portal = { enable = true; extraPortals = [ pkgs.kdePackages.xdg-desktop-portal-kde pkgs.xdg-desktop-portal-gtk ]; config.common.default = [ "kde" ]; };

3. Systemd service to override kde-gtk-config

# In the same gtk.nix systemd.user.services.gtk-icon-theme-override = { Unit = { Description = "Force GTK icon theme to Adwaita (override kde-gtk-config)"; After = [ "plasma-kde-gtk-config.service" "graphical-session.target" ]; }; Service = { Type = "oneshot"; ExecStart = "${pkgs.glib}/bin/gsettings set org.gnome.desktop.interface icon-theme Adwaita"; }; Install = { WantedBy = [ "graphical-session.target" ]; }; };
This runs AFTER KDE's kde-gtk-config writes "breeze" to dconf, overwriting it with "Adwaita". The GTK portal then serves the correct value to GTK4 apps.

Bonus: Nuke stale home-manager backups

If you hit .hm-backup or .hm-bak collision errors during rebuild (common when adding gtk.enable = true for the first time):
# lib/default.nix or wherever home-manager is configured home-manager.backupFileExtension = "hm-bak"; home-manager.sharedModules = [{ home.activation.nukeHmBackups = { before = [ "checkLinkTargets" ]; after = []; data = '' find "$HOME" -maxdepth 3 \( -name "*.hm-backup" -o -name "*.hm-bak" -o -name "*.hm-bak-old" \) -delete 2>/dev/null || true ''; }; }];

Diagnosis Commands

Confirm the problem

GTK_DEBUG=icontheme ghostty 2>&1 | head -100
If you see GTK4 scanning only hicolor directories and /org/gnome/Adwaita/icons/ GResource paths (but never filesystem Adwaita), this is your issue.

Check dconf state

dconf read /org/gnome/desktop/interface/icon-theme # Should say 'Adwaita' — if it says 'breeze', kde-gtk-config overwrote it

Check portal config

cat ~/.config/xdg-desktop-portal/portals.conf # Should exist and contain the [preferred] section

Verify GTK portal is running

systemctl --user status xdg-desktop-portal-gtk # Should be active

Verify override service ran

systemctl --user status gtk-icon-theme-override # Should show completed successfully

Why This Is So Hard

1.
GTK4 and KDE speak different portal languages — an architectural mismatch upstream (KDE MR !233 tracks this)
2.
KDE actively overwrites dconf on every login — fighting a daemon that runs after your config
3.
NixOS's system-level xdg.portal.config doesn't generate a file — forcing user-level config
4.
home-manager's gtk.enable creates backup collisions — requiring pre-activation cleanup
5.
Nobody had documented the full chain — forum posts cover individual layers but not the stack

References

GTK4 Wayland settings source (reads org.gnome.* only): gdk/wayland/gdksettings-wayland.c line 440
Confirmed working May 6, 2026 on NixOS 26.05 + KDE Plasma 6.6.4 + GTK4 4.20.3 + Ghostty 1.3.2-dev.