diff --git a/.phpstan-baseline.php b/.phpstan-baseline.php
new file mode 100644
index 0000000000000000000000000000000000000000..b624e6eeff785959b60c30abe919307f2c243af2
--- /dev/null
+++ b/.phpstan-baseline.php
@@ -0,0 +1,347 @@
+<?php declare(strict_types = 1);
+
+$ignoreErrors = [];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Field\\\\BasicField\\:\\:get_type\\(\\) should return string but returns mixed\\.$#',
+	'identifier' => 'return.type',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Field\\\\BasicField\\:\\:unset_attribute\\(\\) has invalid return type WPDesk\\\\Forms\\\\Field\\\\Traits\\\\HtmlAttributes\\.$#',
+	'identifier' => 'return.trait',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Field\\\\BasicField\\:\\:unset_attribute\\(\\) should return WPDesk\\\\Forms\\\\Field\\\\Traits\\\\HtmlAttributes but returns \\$this\\(WPDesk\\\\Forms\\\\Field\\\\BasicField\\)\\.$#',
+	'identifier' => 'return.type',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'class\' on array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\} on left side of \\?\\? always exists and is not nullable\\.$#',
+	'identifier' => 'nullCoalesce.offset',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'data\' on array\\{default_value\\: string, possible_values\\?\\: array\\<string\\>, sublabel\\?\\: string, priority\\: int, label\\: string, description\\: string, description_tip\\: string, data\\: array\\<int\\|string\\>\\} on left side of \\?\\? always exists and is not nullable\\.$#',
+	'identifier' => 'nullCoalesce.offset',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'default_value\' on array\\{default_value\\: string, possible_values\\?\\: array\\<string\\>, sublabel\\?\\: string, priority\\: int, label\\: string, description\\: string, description_tip\\: string, data\\: array\\<int\\|string\\>\\} on left side of \\?\\? always exists and is not nullable\\.$#',
+	'identifier' => 'nullCoalesce.offset',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'description\' on array\\{default_value\\: string, possible_values\\?\\: array\\<string\\>, sublabel\\?\\: string, priority\\: int, label\\: string, description\\: string, description_tip\\: string, data\\: array\\<int\\|string\\>\\} on left side of \\?\\? always exists and is not nullable\\.$#',
+	'identifier' => 'nullCoalesce.offset',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'description_tip\' on array\\{default_value\\: string, possible_values\\?\\: array\\<string\\>, sublabel\\?\\: string, priority\\: int, label\\: string, description\\: string, description_tip\\: string, data\\: array\\<int\\|string\\>\\} on left side of \\?\\? always exists and is not nullable\\.$#',
+	'identifier' => 'nullCoalesce.offset',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'disabled\' on array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\} on left side of \\?\\? does not exist\\.$#',
+	'identifier' => 'nullCoalesce.offset',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'id\' on array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\} on left side of \\?\\? always exists and is not nullable\\.$#',
+	'identifier' => 'nullCoalesce.offset',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'label\' on array\\{default_value\\: string, possible_values\\?\\: array\\<string\\>, sublabel\\?\\: string, priority\\: int, label\\: string, description\\: string, description_tip\\: string, data\\: array\\<int\\|string\\>\\} on left side of \\?\\? always exists and is not nullable\\.$#',
+	'identifier' => 'nullCoalesce.offset',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'multiple\' on array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\} in isset\\(\\) does not exist\\.$#',
+	'identifier' => 'isset.offset',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'name\' on array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\} on left side of \\?\\? always exists and is not nullable\\.$#',
+	'identifier' => 'nullCoalesce.offset',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'placeholder\' on array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\} on left side of \\?\\? always exists and is not nullable\\.$#',
+	'identifier' => 'nullCoalesce.offset',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'readonly\' on array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\} on left side of \\?\\? does not exist\\.$#',
+	'identifier' => 'nullCoalesce.offset',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'required\' on array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\} in isset\\(\\) does not exist\\.$#',
+	'identifier' => 'isset.offset',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'type\' does not exist on array\\{default_value\\: string, possible_values\\?\\: array\\<string\\>, sublabel\\?\\: string, priority\\: int, label\\: string, description\\: string, description_tip\\: string, data\\: array\\<int\\|string\\>\\}\\.$#',
+	'identifier' => 'offsetAccess.notFound',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset string on array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\} on left side of \\?\\? always exists and is not nullable\\.$#',
+	'identifier' => 'nullCoalesce.offset',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Property WPDesk\\\\Forms\\\\Field\\\\BasicField\\:\\:\\$attributes \\(array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\}\\) does not accept array\\{placeholder\\?\\: string, name\\?\\: string, id\\?\\: string, class\\?\\: array\\<string\\>\\}\\.$#',
+	'identifier' => 'assign.propertyType',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Property WPDesk\\\\Forms\\\\Field\\\\BasicField\\:\\:\\$attributes \\(array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\}\\) does not accept default value of type array\\{\\}\\.$#',
+	'identifier' => 'property.defaultValue',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Property WPDesk\\\\Forms\\\\Field\\\\BasicField\\:\\:\\$attributes \\(array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\}\\) does not accept non\\-empty\\-array\\<string, array\\<string\\>\\|bool\\|string\\>\\.$#',
+	'identifier' => 'assign.propertyType',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Return type \\(array\\<array\\<string\\>\\|bool\\|string\\>\\) of method WPDesk\\\\Forms\\\\Field\\\\BasicField\\:\\:get_attributes\\(\\) should be covariant with return type \\(array\\<string\\>\\) of method WPDesk\\\\Forms\\\\Field\\:\\:get_attributes\\(\\)$#',
+	'identifier' => 'method.childReturnType',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/BasicField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'sublabel\' might not exist on array\\{default_value\\: string, possible_values\\?\\: array\\<string\\>, sublabel\\?\\: string, priority\\: int, label\\: string, description\\: string, description_tip\\: string, data\\: array\\<int\\|string\\>\\}\\.$#',
+	'identifier' => 'offsetAccess.notFound',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/CheckboxField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Parameter \\#1 \\$action of class WPDesk\\\\Forms\\\\Validator\\\\NonceValidator constructor expects string, mixed given\\.$#',
+	'identifier' => 'argument.type',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Field/NoOnceField.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\FieldRenderer\\:\\:render_fields\\(\\) has parameter \\$fields_data with no value type specified in iterable type array\\.$#',
+	'identifier' => 'missingType.iterableValue',
+	'count' => 1,
+	'path' => __DIR__ . '/src/FieldRenderer.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\FieldRenderer\\:\\:render_fields\\(\\) return type has no value type specified in iterable type array\\.$#',
+	'identifier' => 'missingType.iterableValue',
+	'count' => 1,
+	'path' => __DIR__ . '/src/FieldRenderer.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Form\\:\\:get_normalized_data\\(\\) return type has no value type specified in iterable type array\\.$#',
+	'identifier' => 'missingType.iterableValue',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Form.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Form\\:\\:handle_request\\(\\) has parameter \\$request with no value type specified in iterable type array\\.$#',
+	'identifier' => 'missingType.iterableValue',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Form.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Form\\\\FormWithFields\\:\\:get_action\\(\\) should return string but returns mixed\\.$#',
+	'identifier' => 'return.type',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Form/FormWithFields.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Form\\\\FormWithFields\\:\\:get_data\\(\\) should return array\\<int\\|string\\> but returns array\\.$#',
+	'identifier' => 'return.type',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Form/FormWithFields.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Form\\\\FormWithFields\\:\\:get_method\\(\\) should return string but returns mixed\\.$#',
+	'identifier' => 'return.type',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Form/FormWithFields.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Form\\\\FormWithFields\\:\\:get_normalized_data\\(\\) return type has no value type specified in iterable type array\\.$#',
+	'identifier' => 'missingType.iterableValue',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Form/FormWithFields.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Form\\\\FormWithFields\\:\\:handle_request\\(\\) has parameter \\$request with no value type specified in iterable type array\\.$#',
+	'identifier' => 'missingType.iterableValue',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Form/FormWithFields.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Form\\\\FormWithFields\\:\\:unset_attribute\\(\\) has invalid return type WPDesk\\\\Forms\\\\Field\\\\Traits\\\\HtmlAttributes\\.$#',
+	'identifier' => 'return.trait',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Form/FormWithFields.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Form\\\\FormWithFields\\:\\:unset_attribute\\(\\) should return WPDesk\\\\Forms\\\\Field\\\\Traits\\\\HtmlAttributes but returns \\$this\\(WPDesk\\\\Forms\\\\Form\\\\FormWithFields\\)\\.$#',
+	'identifier' => 'return.type',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Form/FormWithFields.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'action\' does not exist on array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\}\\.$#',
+	'identifier' => 'offsetAccess.notFound',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Form/FormWithFields.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset \'method\' does not exist on array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\}\\.$#',
+	'identifier' => 'offsetAccess.notFound',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Form/FormWithFields.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Offset string on array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\} on left side of \\?\\? always exists and is not nullable\\.$#',
+	'identifier' => 'nullCoalesce.offset',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Form/FormWithFields.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Parameter \\#2 \\$value of method WPDesk\\\\Persistence\\\\PersistentContainer\\:\\:set\\(\\) expects array\\|float\\|int\\|string, mixed given\\.$#',
+	'identifier' => 'argument.type',
+	'count' => 2,
+	'path' => __DIR__ . '/src/Form/FormWithFields.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Property WPDesk\\\\Forms\\\\Form\\\\FormWithFields\\:\\:\\$attributes \\(array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\}\\) does not accept array\\{placeholder\\?\\: string, name\\?\\: string, id\\?\\: string, class\\?\\: array\\<string\\>\\}\\.$#',
+	'identifier' => 'assign.propertyType',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Form/FormWithFields.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Property WPDesk\\\\Forms\\\\Form\\\\FormWithFields\\:\\:\\$attributes \\(array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\}\\) does not accept default value of type array\\{\\}\\.$#',
+	'identifier' => 'property.defaultValue',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Form/FormWithFields.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Property WPDesk\\\\Forms\\\\Form\\\\FormWithFields\\:\\:\\$attributes \\(array\\{placeholder\\: string, name\\: string, id\\: string, class\\: array\\<string\\>\\}\\) does not accept non\\-empty\\-array\\<string, array\\<string\\>\\|bool\\|string\\>\\.$#',
+	'identifier' => 'assign.propertyType',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Form/FormWithFields.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Property WPDesk\\\\Forms\\\\Form\\\\FormWithFields\\:\\:\\$updated_data type has no value type specified in iterable type array\\.$#',
+	'identifier' => 'missingType.iterableValue',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Form/FormWithFields.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Persistence\\\\FieldPersistenceStrategy\\:\\:load_fields\\(\\) return type has no value type specified in iterable type array\\.$#',
+	'identifier' => 'missingType.iterableValue',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Persistence/FieldPersistenceStrategy.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Persistence\\\\FieldPersistenceStrategy\\:\\:persist_fields\\(\\) has parameter \\$data with no value type specified in iterable type array\\.$#',
+	'identifier' => 'missingType.iterableValue',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Persistence/FieldPersistenceStrategy.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^PHPDoc tag @return with type void is incompatible with native type array\\.$#',
+	'identifier' => 'return.phpDocType',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Persistence/FieldPersistenceStrategy.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Parameter \\#1 \\$value of method WPDesk\\\\Forms\\\\Serializer\\:\\:unserialize\\(\\) expects string, mixed given\\.$#',
+	'identifier' => 'argument.type',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Persistence/FieldPersistenceStrategy.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Parameter \\#2 \\$value of method WPDesk\\\\Persistence\\\\PersistentContainer\\:\\:set\\(\\) expects array\\|float\\|int\\|string, mixed given\\.$#',
+	'identifier' => 'argument.type',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Persistence/FieldPersistenceStrategy.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Renderer\\\\JsonNormalizedRenderer\\:\\:render_fields\\(\\) has parameter \\$fields_data with no value type specified in iterable type array\\.$#',
+	'identifier' => 'missingType.iterableValue',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Renderer/JsonNormalizedRenderer.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Renderer\\\\JsonNormalizedRenderer\\:\\:render_fields\\(\\) return type has no value type specified in iterable type array\\.$#',
+	'identifier' => 'missingType.iterableValue',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Renderer/JsonNormalizedRenderer.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Sanitizer\\\\CallableSanitizer\\:\\:sanitize\\(\\) should return string but returns mixed\\.$#',
+	'identifier' => 'return.type',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Sanitizer/CallableSanitizer.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Parameter \\#1 \\$email of function sanitize_email expects string, mixed given\\.$#',
+	'identifier' => 'argument.type',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Sanitizer/EmailSanitizer.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Parameter \\#1 \\$callback of function array_map expects \\(callable\\(mixed\\)\\: mixed\\)\\|null, \'sanitize_text_field\' given\\.$#',
+	'identifier' => 'argument.type',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Sanitizer/TextFieldSanitizer.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Parameter \\#1 \\$str of function sanitize_text_field expects string, mixed given\\.$#',
+	'identifier' => 'argument.type',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Sanitizer/TextFieldSanitizer.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Method WPDesk\\\\Forms\\\\Validator\\\\ChainValidator\\:\\:get_messages\\(\\) should return array\\<string\\> but returns array\\.$#',
+	'identifier' => 'return.type',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Validator/ChainValidator.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Property WPDesk\\\\Forms\\\\Validator\\\\ChainValidator\\:\\:\\$messages type has no value type specified in iterable type array\\.$#',
+	'identifier' => 'missingType.iterableValue',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Validator/ChainValidator.php',
+];
+$ignoreErrors[] = [
+	'message' => '#^Parameter \\#1 \\$nonce of function wp_verify_nonce expects string, mixed given\\.$#',
+	'identifier' => 'argument.type',
+	'count' => 1,
+	'path' => __DIR__ . '/src/Validator/NonceValidator.php',
+];
+
+return ['parameters' => ['ignoreErrors' => $ignoreErrors]];
diff --git a/changelog.txt b/changelog.txt
index c21b67cfeb3a0ea78d21e852f119709f861baebd..bf68f37b287f3b55f69d663661ca549a1f45dfa4 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,5 +1,11 @@
 # Changelog
 
+## [3.3.0] - 2025-04-15
+### Added
+- ToggleField class, extends CheckboxField for toggle button functionality.
+### Changed
+- Declared nullable parameter types (e.g. `?string`, `?Renderer`) to fix PHP 8.4 deprecation warnings about implicitly nullable parameters.
+
 ## [3.2.1] - 2024-10-30
 ### Changed
 - Raised required PHP version to 7.4.
diff --git a/composer.json b/composer.json
index 92744224c28f499788ba57082ef0ee1d5066f7a2..874f03ffd1d1037ca017511a6365dbc48d244a90 100644
--- a/composer.json
+++ b/composer.json
@@ -44,6 +44,9 @@
 		}
 	},
 	"scripts": {
+		"phpcs": "phpcs",
+		"phpstan": "php -d memory_limit=1G vendor/bin/phpstan",
+		"phpcbf": "phpcbf src",
 		"phpunit-unit": "phpunit --configuration phpunit-unit.xml --coverage-text --colors=never",
 		"phpunit-unit-fast": "phpunit --configuration phpunit-unit.xml --no-coverage",
 		"phpunit-integration": "phpunit --configuration phpunit-integration.xml --coverage-text --colors=never",
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
index 200d200a6b18db3d5abbcd4e9ee9edbd96924c25..422f90cabdc9d2be2923a706619d4c95590de1a0 100644
--- a/phpstan.neon.dist
+++ b/phpstan.neon.dist
@@ -1,3 +1,5 @@
+includes:
+    - .phpstan-baseline.php
 parameters:
     level: max
     paths:
diff --git a/src/Field.php b/src/Field.php
index b932f273f01a7dff40ffeedd12be722d943e0869..14b02592ef679ab0a26d4cbb843433a6fef22d54 100644
--- a/src/Field.php
+++ b/src/Field.php
@@ -55,7 +55,7 @@ interface Field {
 	 */
 	public function get_attributes( array $except = [] ): array;
 
-	public function get_attribute( string $name, string $default = null ): string;
+	public function get_attribute( string $name, ?string $default = null ): string;
 
 	public function is_attribute_set( string $name ): bool;
 
diff --git a/src/Field/ToggleField.php b/src/Field/ToggleField.php
new file mode 100644
index 0000000000000000000000000000000000000000..79ff440bb27bd98ede1e6fedab22b42d6544c32a
--- /dev/null
+++ b/src/Field/ToggleField.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace WPDesk\Forms\Field;
+
+class ToggleField extends CheckboxField {
+
+	public function __construct() {
+		$this->add_class( 'wpd-toggle-field' );
+	}
+
+	public function get_template_name(): string {
+		return 'input-toggle';
+	}
+}
diff --git a/src/Field/Traits/HtmlAttributes.php b/src/Field/Traits/HtmlAttributes.php
index aac835dbaf3389d38cd4477a3e239840138285ce..95a7a421eddb770d860ad39b657df41d6da4ffef 100644
--- a/src/Field/Traits/HtmlAttributes.php
+++ b/src/Field/Traits/HtmlAttributes.php
@@ -57,7 +57,7 @@ trait HtmlAttributes {
 		return ! empty( $this->attributes[ $name ] );
 	}
 
-	final public function get_attribute( string $name, string $default = null ): string {
+	final public function get_attribute( string $name, ?string $default = null ): string {
 		if ( is_array( $this->attributes[ $name ] ) ) {
 			// Be aware of coercing - if implode returns string(0) '', then return $default value.
 			return implode( ' ', $this->attributes[ $name ] ) ?: $default ?? '';
diff --git a/src/Persistence/FieldPersistenceStrategy.php b/src/Persistence/FieldPersistenceStrategy.php
index 3102f1ce6f69d8f9dc6f790a9a885e92076d3e39..7fa7dcc76587bd2e68d201f29ef34e2dcd276a5c 100644
--- a/src/Persistence/FieldPersistenceStrategy.php
+++ b/src/Persistence/FieldPersistenceStrategy.php
@@ -43,9 +43,7 @@ class FieldPersistenceStrategy {
 				} else {
 					$data[ $field_key ] = $this->persistence->get( $field_key );
 				}
-			} catch ( NotFoundExceptionInterface $not_found ) {
-				// TODO: Logger
-				// LoggerFactory::get_logger()->info( "FieldPersistenceStrategy:: Field {$field_key} not found" );
+			} catch ( NotFoundExceptionInterface $not_found ) { //phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
 			}
 		}
 
diff --git a/src/Resolver/DefaultFormFieldResolver.php b/src/Resolver/DefaultFormFieldResolver.php
index 4e3077a0cb2b87ef000ac269d3411a9e5a1cf70b..23f5a522011d6ff4a0b92936446c0aeed625051d 100644
--- a/src/Resolver/DefaultFormFieldResolver.php
+++ b/src/Resolver/DefaultFormFieldResolver.php
@@ -20,7 +20,7 @@ class DefaultFormFieldResolver implements Resolver {
 		$this->dir_resolver = new DirResolver( __DIR__ . '/../../templates' );
 	}
 
-	public function resolve( $name, Renderer $renderer = null ): string {
+	public function resolve( $name, ?Renderer $renderer = null ): string {
 		return $this->dir_resolver->resolve( $name, $renderer );
 	}
 }
diff --git a/templates/input-toggle.php b/templates/input-toggle.php
new file mode 100644
index 0000000000000000000000000000000000000000..848cdf819581485a2b5eeceb54b648c11bf1d2a8
--- /dev/null
+++ b/templates/input-toggle.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * @var \WPDesk\Forms\Field $field
+ * @var \WPDesk\View\Renderer\Renderer $renderer
+ * @var string $name_prefix
+ * @var string $value
+ * @var string $template_name Real field template.
+ */
+
+?>
+
+<style>
+	input[type="checkbox"].wpd-toggle-field {
+		all:unset;
+		background: #fff;
+		border:1px solid #949494;
+		width:32px;
+		height: 16px;
+		position: relative;
+		cursor: pointer;
+		border-radius: 200px;
+	}
+	input[type="checkbox"].wpd-toggle-field::before {
+		all:unset;
+		content: "";
+		display: block !important;
+		height: 14px;
+		width:14px;
+		background: #1c1c1c;
+		border-radius: 100%;
+		position: absolute;
+		left: 1px;
+		top:1px;
+		transition: 0.25s;
+		transition-timing-function: ease-out;
+	}
+	input[type="checkbox"].wpd-toggle-field:checked {
+		background: var(--wp-admin-theme-color, #1851e0);
+		border-color: var(--wp-admin-theme-color, #1851e0);
+	}
+	input[type="checkbox"].wpd-toggle-field:checked:before {
+		left: 17px;
+		background: #fff;
+	}
+
+</style>
+<?php
+$renderer->output_render(
+	'input',
+	[
+		'field'       => $field,
+		'renderer'    => $renderer,
+		'name_prefix' => $name_prefix,
+		'value'       => $value,
+	]
+);