diff --git a/.luacheckrc b/.luacheckrc
index f532e1bfd318134124a9753b2a65f756512ad1b7..b308748c594986b4b2234009fc2e90b90c1f80e9 100644
--- a/.luacheckrc
+++ b/.luacheckrc
@@ -110,5 +110,6 @@ files["package/features"] = {
 	read_globals = {
 		"_",
 		"feature",
+		"when",
 	},
 }
diff --git a/docs/dev/packages.rst b/docs/dev/packages.rst
index af047e2170d025f6746c896e284f5115299bdc70..b6032b2e3232c1c7517a25a93daef3a8e9189ff9 100644
--- a/docs/dev/packages.rst
+++ b/docs/dev/packages.rst
@@ -82,43 +82,43 @@ Each flag *$flag* will include the package the name *gluon-$flag* by default.
 The feature definition file can modify the package selection by adding or removing
 packages when certain combinations of flags are set.
 
-Feature definitions use Lua syntax. The function *feature* has two arguments:
+Feature definitions use Lua syntax. Two basic functions are defined:
 
-* A logical expression composed of feature flag names (each prefixed with an underscore before the opening
-  quotation mark), logical operators (*and*, *or*, *not*) and parentheses
-* A table with settings that are applied when the logical expression is
-  satisfied:
+* *feature(name, pkgs)*: Defines a new feature. *feature()* expects a feature
+  (flag) name and a list of packages to add or remove when the feature is
+  enabled.
 
-  * Setting *nodefault* to *true* suppresses the default of including the *gluon-$flag* package.
-    This setting is only applicable when the logical expression is a single,
-    non-negated flag name.
-  * The *packages* field adds or removes packages to install. A package is
-    removed when the package name is prefixed with a ``-`` (after the opening
-    quotation mark).
+  * Defining a feature using *feature* replaces the default definition of
+    just including *gluon-$flag*.
+  * A package is removed when the package name is prefixed with a ``-`` (after
+    the opening quotation mark).
+
+* *when(expr, pkgs)*: Adds or removes packages when a given logical expression
+  of feature flags is satisfied.
+
+  * *expr* is a logical expression composed of feature flag names (each prefixed
+    with an underscore before the opening quotation mark), logical operators
+    (*and*, *or*, *not*) and parentheses.
+  * Referencing a feature flag in *expr* has no effect on the default handling
+    of the flag. When no *feature()* entry for a flag exists, it will still
+    add *gluon-$flag* by default.
+  * *pkgs* is handled as for *feature()*.
 
 Example::
 
-    feature(_'web-wizard', {
-      nodefault = true,
-      packages = {
-        'gluon-config-mode-hostname',
-        'gluon-config-mode-geo-location',
-        'gluon-config-mode-contact-info',
-        'gluon-config-mode-outdoor',
-      },
+    feature('web-wizard', {
+      'gluon-config-mode-hostname',
+      'gluon-config-mode-geo-location',
+      'gluon-config-mode-contact-info',
+      'gluon-config-mode-outdoor',
     })
 
-    feature(_'web-wizard' and (_'mesh-vpn-fastd' or _'mesh-vpn-tunneldigger'), {
-      packages = {
-        'gluon-config-mode-mesh-vpn',
-      },
+    when(_'web-wizard' and (_'mesh-vpn-fastd' or _'mesh-vpn-tunneldigger'), {
+      'gluon-config-mode-mesh-vpn',
     })
 
-    feature(_'no-radvd', {
-      nodefault = true,
-      packages = {
-        '-gluon-radvd',
-      },
+    feature('no-radvd', {
+      '-gluon-radvd',
     })
 
 
diff --git a/package/features b/package/features
index 665b1bd6324fe80a774b3e05f0b996b38bf18abf..72887e3af03c8d2374c3e509c1e8468c01502f0c 100644
--- a/package/features
+++ b/package/features
@@ -5,65 +5,48 @@
 -- file format
 
 
-feature(_'web-wizard', {
-	nodefault = true,
-	packages = {
-		'gluon-config-mode-hostname',
-		'gluon-config-mode-geo-location',
-		'gluon-config-mode-contact-info',
-		'gluon-config-mode-outdoor',
-	},
+feature('web-wizard', {
+	'gluon-config-mode-hostname',
+	'gluon-config-mode-geo-location',
+	'gluon-config-mode-contact-info',
+	'gluon-config-mode-outdoor',
 })
 
-feature(_'web-wizard' and _'autoupdater', {
-	packages = {
-		'gluon-config-mode-autoupdater',
-	},
+when(_'web-wizard' and _'autoupdater', {
+	'gluon-config-mode-autoupdater',
 })
 
-feature(_'web-wizard' and (_'mesh-vpn-fastd' or _'mesh-vpn-tunneldigger'), {
-	packages = {
-		'gluon-config-mode-mesh-vpn',
-	},
+when(_'web-wizard' and (_'mesh-vpn-fastd' or _'mesh-vpn-tunneldigger'), {
+	'gluon-config-mode-mesh-vpn',
 })
 
 
-feature(_'web-advanced', {
-	nodefault = true,
-	packages = {
-		'gluon-web-admin',
-		'gluon-web-network',
-		'gluon-web-wifi-config',
-	},
+feature('web-advanced', {
+	'gluon-web-admin',
+	'gluon-web-network',
+	'gluon-web-wifi-config',
 })
 
-feature(_'web-advanced' and _'autoupdater', {
-	packages = {
-		'gluon-web-autoupdater',
-	},
+when(_'web-advanced' and _'autoupdater', {
+	'gluon-web-autoupdater',
 })
 
-feature(_'status-page' and _'mesh-batman-adv-15', {
-	packages = {
-		'gluon-status-page-mesh-batman-adv',
-	},
+
+when(_'mesh-batman-adv-15', {
+	'gluon-ebtables-limit-arp',
+	'gluon-radvd',
 })
 
-feature(_'mesh-batman-adv-15', {
-	packages = {
-		'gluon-ebtables-limit-arp',
-		'gluon-radvd',
-	},
+when(_'status-page' and _'mesh-batman-adv-15', {
+	'gluon-status-page-mesh-batman-adv',
 })
 
-feature(_'mesh-babel', {
-	packages = {
-		'gluon-radvd',
-	},
+
+when(_'mesh-babel', {
+	'gluon-radvd',
 })
 
-feature(not _'wireless-encryption-wpa3', {
-	packages = {
-		'hostapd-mini',
-	},
+
+when(not _'wireless-encryption-wpa3', {
+	'hostapd-mini',
 })
diff --git a/scripts/feature_lib.lua b/scripts/feature_lib.lua
index d4cfafe0124821e23675b417a674f9b01319268c..4c575dd470dc16a80dda279c13a245550f45c193 100644
--- a/scripts/feature_lib.lua
+++ b/scripts/feature_lib.lua
@@ -17,28 +17,41 @@ local function collect_keys(t)
 end
 
 function M.get_packages(file, features)
-	local feature_table = to_keys(features)
+	local enabled_features = to_keys(features)
+	local handled_features = {}
+	local packages = {}
 
 	local funcs = {}
 
-	function funcs._(feature)
-		if feature_table[feature] then
-			return feature
+	local function add_pkgs(pkgs)
+		for _, pkg in ipairs(pkgs or {}) do
+			packages[pkg] = true
 		end
 	end
 
-	local nodefault = {}
-	local packages = {}
-	function funcs.feature(match, options)
-		if not match then
-			return
-		end
+	function funcs._(feature)
+		return enabled_features[feature] ~= nil
+	end
 
-		if options.nodefault then
-			nodefault[match] = true
+	function funcs.feature(feature, pkgs)
+		assert(
+			type(feature) == 'string',
+			'Incorrect use of feature(): pass a feature name without _ as first argument')
+
+		if enabled_features[feature] then
+			handled_features[feature] = true
+			add_pkgs(pkgs)
 		end
-		for _, package in ipairs(options.packages or {}) do
-			packages[package] = true
+
+	end
+
+	function funcs.when(cond, pkgs)
+		assert(
+			type(cond) == 'boolean',
+			'Incorrect use of when(): pass a locical expression of _-prefixed strings as first argument')
+
+		if cond then
+			add_pkgs(pkgs)
 		end
 	end
 
@@ -52,7 +65,7 @@ function M.get_packages(file, features)
 
 	-- Handle default packages
 	for _, feature in ipairs(features) do
-		if not nodefault[feature] then
+		if not handled_features[feature] then
 			packages['gluon-' .. feature] = true
 		end
 	end