diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000000000000000000000000000000000000..3fe55c887ecc46752a9b1f64b0f31e256f647207
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,91 @@
+# This file is for unifying the coding style for different editors and IDEs
+# editorconfig.org
+
+# WordPress Coding Standards
+# https://make.wordpress.org/core/handbook/coding-standards/
+
+# Configuration for PhpStorm
+
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+indent_style = tab
+indent_size = 4
+tab_width = 4
+ij_continuation_indent_size = 4
+
+[*.scss]
+ij_scss_enforce_quotes_on_format = true
+ij_scss_hex_color_long_format = true
+ij_scss_hex_color_lower_case = true
+
+[{*.cjs,*.js}]
+indent_style = space
+indent_size = 2
+ij_javascript_do_while_brace_force = always
+ij_javascript_if_brace_force = always
+ij_javascript_import_sort_module_name = true
+ij_javascript_reformat_c_style_comments = true
+ij_javascript_space_after_unary_not = true
+ij_javascript_space_before_unary_not = true
+ij_javascript_spaces_within_array_initializer_brackets = true
+ij_javascript_spaces_within_brackets = true
+ij_javascript_spaces_within_catch_parentheses = true
+ij_javascript_spaces_within_for_parentheses = true
+ij_javascript_spaces_within_if_parentheses = true
+ij_javascript_spaces_within_imports = true
+ij_javascript_spaces_within_interpolation_expressions = true
+ij_javascript_spaces_within_method_call_parentheses = true
+ij_javascript_spaces_within_method_parentheses = true
+ij_javascript_spaces_within_object_literal_braces = true
+ij_javascript_spaces_within_parentheses = true
+ij_javascript_spaces_within_switch_parentheses = true
+ij_javascript_spaces_within_while_parentheses = true
+ij_javascript_while_brace_force = always
+
+[{*.ctp,*.hphp,*.inc,*.module,*.php,*.php4,*.php5,*.phtml}]
+ij_php_align_assignments = true
+ij_php_align_class_constants = true
+ij_php_align_key_value_pairs = true
+ij_php_align_phpdoc_comments = true
+ij_php_align_phpdoc_param_names = true
+ij_php_class_brace_style = end_of_line
+ij_php_comma_after_last_array_element = true
+ij_php_else_if_style = combine
+ij_php_force_short_declaration_array_style = true
+ij_php_getters_setters_naming_style = snake_case
+ij_php_if_brace_force = always
+ij_php_lower_case_boolean_const = true
+ij_php_lower_case_null_const = true
+ij_php_method_brace_style = end_of_line
+ij_php_phpdoc_blank_line_before_tags = true
+ij_php_phpdoc_blank_lines_around_parameters = true
+ij_php_phpdoc_keep_blank_lines = false
+ij_php_phpdoc_wrap_long_lines = true
+ij_php_space_after_type_cast = true
+ij_php_space_after_unary_not = true
+ij_php_space_before_unary_not = true
+ij_php_spaces_around_var_within_brackets = true
+ij_php_spaces_within_array_initializer_braces = true
+ij_php_spaces_within_catch_parentheses = true
+ij_php_spaces_within_for_parentheses = true
+ij_php_spaces_within_if_parentheses = true
+ij_php_spaces_within_method_call_parentheses = true
+ij_php_spaces_within_method_parentheses = true
+ij_php_spaces_within_parentheses = true
+ij_php_spaces_within_switch_parentheses = true
+ij_php_spaces_within_while_parentheses = true
+ij_php_ternary_operation_signs_on_next_line = true
+ij_php_variable_naming_style = snake_case
+
+[{*.htm,*.html,*.ng,*.sht,*.shtm,*.shtml}]
+ij_html_add_new_line_before_tags = div,p,a
+ij_html_align_attributes = false
+ij_html_do_not_indent_children_of_tags = html,thead,tbody,tfoot
+ij_html_new_line_after_last_attribute = when multiline
+ij_html_space_inside_empty_tag = true
+ij_html_uniform_ident = true
diff --git a/.env.testing b/.env.testing
new file mode 100644
index 0000000000000000000000000000000000000000..3aa2f5f455a4ead5271cb5c983ec33844801b86d
--- /dev/null
+++ b/.env.testing
@@ -0,0 +1,17 @@
+WP_ROOT_FOLDER="${APACHE_DOCUMENT_ROOT}"
+TEST_SITE_WP_ADMIN_PATH="/wp-admin"
+TEST_SITE_DB_NAME="wptest"
+TEST_SITE_DB_HOST="mysqltests"
+TEST_SITE_DB_USER="mysql"
+TEST_SITE_DB_PASSWORD="mysql"
+TEST_SITE_TABLE_PREFIX="wp_"
+TEST_DB_NAME="wptest"
+TEST_DB_HOST="mysqltests"
+TEST_DB_USER="mysql"
+TEST_DB_PASSWORD="mysql"
+TEST_TABLE_PREFIX="wp_"
+TEST_SITE_WP_URL="http://${WOOTESTS_IP}"
+TEST_SITE_WP_DOMAIN="${WOOTESTS_IP}"
+TEST_SITE_ADMIN_EMAIL="grola@seostudio.pl"
+TEST_SITE_ADMIN_USERNAME="admin"
+TEST_SITE_ADMIN_PASSWORD="admin"
diff --git a/.gitignore b/.gitignore
index 4b86223d6cae8ddb86d7cc3d12e0fcca43717f54..16e7a8c8b2234275f15fcd36090ce9e2badf7e36 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,5 @@
 .idea
 composer.lock
 build-coverage
-swagger
\ No newline at end of file
+swagger
+.phpcs-cache
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 38447e9e09d3e045220f8589039a98f7368dba3b..b865367b37c950486d9171cbdfa3d7f1a922fc6a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -3,7 +3,6 @@ variables:
   DISABLE_ACCEPTANCE: 1
   DISABLE_PHP_5_5: 1
   DISABLE_CODECEPTION: 1
-  DISABLE_INTEGRATION_TESTS: 1
   IS_LIBRARY: 1
 
 include: 'https://gitlab.com/wpdesk/gitlab-ci/raw/master/gitlab-ci-1.2.yml'
diff --git a/README.md b/README.md
index 06702bf6be3f5cdda326d517e693e8d9d184fd18..c129e94afbe18c2c8ba172848d73a8b89bec7bf3 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
-[![pipeline status](https://gitlab.com/wpdesk/wp-forms/badges/master/pipeline.svg)](https://gitlab.com/wpdesk/wp-forms/pipelines) 
-[![coverage report](https://gitlab.com/wpdesk/wp-forms/badges/master/coverage.svg)](https://gitlab.com/wpdesk/wp-forms/commits/master) 
-[![Latest Stable Version](https://poser.pugx.org/wpdesk/wp-forms/v/stable)](https://packagist.org/packages/wpdesk/wp-forms) 
-[![Total Downloads](https://poser.pugx.org/wpdesk/wp-forms/downloads)](https://packagist.org/packages/wpdesk/wp-forms) 
-[![Latest Unstable Version](https://poser.pugx.org/wpdesk/wp-forms/v/unstable)](https://packagist.org/packages/wpdesk/wp-forms) 
+[![pipeline status](https://gitlab.com/wpdesk/wp-forms/badges/master/pipeline.svg)](https://gitlab.com/wpdesk/wp-forms/pipelines)
+[![coverage report](https://gitlab.com/wpdesk/wp-forms/badges/master/coverage.svg)](https://gitlab.com/wpdesk/wp-forms/commits/master)
+[![Latest Stable Version](https://poser.pugx.org/wpdesk/wp-forms/v/stable)](https://packagist.org/packages/wpdesk/wp-forms)
+[![Total Downloads](https://poser.pugx.org/wpdesk/wp-forms/downloads)](https://packagist.org/packages/wpdesk/wp-forms)
+[![Latest Unstable Version](https://poser.pugx.org/wpdesk/wp-forms/v/unstable)](https://packagist.org/packages/wpdesk/wp-forms)
 [![License](https://poser.pugx.org/wpdesk/wp-forms/license)](https://packagist.org/packages/wpdesk/wp-forms)
 
 WordPress Library for Form integration
@@ -11,7 +11,7 @@ WordPress Library for Form integration
 
 ## Requirements
 
-PHP 5.6 or later.
+PHP 7.0 or later.
 
 ## Composer
 
@@ -34,35 +34,27 @@ Let's say we have an abstraction for settings tabs:
 interface SettingsTab {
 	/**
 	 * Slug name used for unique url and settings in db.
-	 *
-	 * @return string
 	 */
-	public static function get_tab_slug();
+	public static function get_tab_slug(): string;
 
 	/**
 	 * Tab name to show on settings page.
-	 *
-	 * @return string
 	 */
-	public function get_tab_name();
+	public function get_tab_name(): string;
 
 	/**
 	 * Render tab content and return it as string.
-	 *
-	 * @param Renderer $renderer
-	 *
-	 * @return string
 	 */
-	public function render( Renderer $renderer );
+	public function render( Renderer $renderer ): string;
 
 	/**
 	 * Use to set settings from database or defaults.
 	 *
-	 * @param array|ContainerInterface $data Data to render.
+	 * @param ContainerInterface $data Data to render.
 	 *
 	 * @return void
 	 */
-	public function set_data( $data );
+	public function set_data( ContainerInterface $data );
 
 	/**
 	 * Use to handle request data from POST.
@@ -74,14 +66,14 @@ interface SettingsTab {
 	 *
 	 * @return void
 	 */
-	public function handle_request( $request );
+	public function handle_request( array $request );
 
 	/**
 	 * Returns valid data from Tab. Can be used after ::handle_request or ::set_data.
 	 *
 	 * @return array
 	 */
-	public function get_data();
+	public function get_data(): array;
 }
 ```
 
@@ -95,12 +87,9 @@ abstract class FieldSettingsTab implements SettingsTab {
 	/**
 	 * @return Field[]
 	 */
-	abstract protected function get_fields();
+	abstract protected function get_fields(): array;
 
-	/**
-	 * @return FormWithFields
-	 */
-	protected function get_form() {
+	protected function get_form(): FormWithFields {
 		if ( $this->form === null ) {
 			$fields     = $this->get_fields();
 			$this->form = new FormWithFields( $fields, static::get_tab_slug() );
@@ -109,19 +98,19 @@ abstract class FieldSettingsTab implements SettingsTab {
 		return $this->form;
 	}
 
-	public function render( Renderer $renderer) {
+	public function render( Renderer $renderer ) {
 		return $this->get_form()->render_form( $renderer );
 	}
 
-	public function set_data( $data ) {
+	public function set_data( ContainerInterface $data ) {
 		$this->get_form()->set_data( $data );
 	}
 
-	public function handle_request( $request ) {
+	public function handle_request( array $request ) {
 		$this->get_form()->handle_request( $request );
 	}
 
-	public function get_data() {
+	public function get_data(): array {
 		return $this->get_form()->get_data();
 	}
 }
@@ -131,10 +120,7 @@ Then we can create a settings tab that looks like that:
 
 ```php
 final class GeneralSettings extends FieldSettingsTab {
-	/**
-	 * @inheritDoc
-	 */
-	protected function get_fields() {
+	protected function get_fields(): array {
 		return [
 			( new CheckboxField() )
 				->set_label( __( 'Subscribe on checkout', 'some-text-domain' ) )
@@ -166,23 +152,17 @@ final class GeneralSettings extends FieldSettingsTab {
 		];
 	}
 
-	/**
-	 * @inheritDoc
-	 */
 	public static function get_tab_slug() {
 		return 'general';
 	}
 
-	/**
-	 * @inheritDoc
-	 */
 	public function get_tab_name() {
 		return __( 'General', 'text-domain' );
 	}
 }
 ```
 
-Then class like that provides form load/save/render support for these abstraction can look like this:
+Then class like that provides form load/save/render support for this abstraction can look like this:
 
 ```php
 /**
@@ -212,8 +192,6 @@ final class Settings {
 	}
 
 	/**
-	 * Render
-	 *
 	 * @return void
 	 */
 	public function render_page_action() {
@@ -228,10 +206,7 @@ final class Settings {
 		echo $renderer->render( 'footer' );
 	}
 
-	/**
-	 * @return SettingsTab
-	 */
-	private function get_active_tab() {
+	private function get_active_tab(): SettingTab {
 		$selected_tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : null;
 		$tabs         = $this->get_settings_tabs();
 		if ( ! empty( $selected_tab ) && isset( $tabs[ $selected_tab ] ) ) {
@@ -244,7 +219,7 @@ final class Settings {
 	/**
 	 * @return SettingsTab[]
 	 */
-	private function get_settings_tabs() {
+	private function get_settings_tabs(): array {
 		static $tabs = [];
 		if ( empty( $tabs ) ) {
 			$tabs = [
@@ -263,15 +238,12 @@ final class Settings {
 	 *
 	 * @return PersistentContainer
 	 */
-	public static function get_settings_persistence( $tab_slug ) {
+	public static function get_settings_persistence( string $tab_slug ): PersistentContainer {
 		return new WordpressOptionsContainer( 'some-settings-' . $tab_slug );
 	}
 
 	/**
 	 * Save data from tab to persistent container.
-	 *
-	 * @param SettingsTab $tab
-	 * @param PersistentContainer $container
 	 */
 	private function save_tab_data( SettingsTab $tab, PersistentContainer $container ) {
 		$tab_data = $tab->get_data();
@@ -282,17 +254,14 @@ final class Settings {
 		} );
 	}
 
-	/**
-	 * @return \WPDesk\View\Renderer\Renderer
-	 */
-	private function get_renderer() {
+	private function get_renderer(): \WPDesk\View\Renderer\Renderer {
 		return new SimplePhpRenderer( new DefaultFormFieldResolver() );
 	}
 
 	/**
 	 * @return string[]
 	 */
-	private function get_tabs_menu_items() {
+	private function get_tabs_menu_items(): array {
 		$menu_items = [];
 
 		foreach ( $this->get_settings_tabs() as $tab ) {
@@ -303,8 +272,3 @@ final class Settings {
 	}
 }
 ```
-
-
-## Project documentation
-
-PHPDoc: https://wpdesk.gitlab.io/wp-forms/index.html  
\ No newline at end of file
diff --git a/changelog.txt b/changelog.txt
index 167c5b781acd7011e139a14e479b8fb81846aa55..d940f97eb6528ea14c26fbb21fbd927b87be2a82 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,5 +1,18 @@
 # Changelog
 
+## [3.0.0] - 2022-07-19
+### Added
+- Added strong typing to all the interfaces
+- Added fields sorting by priority field
+- Normalized string escaping in all template files
+- Added InputEmailField and EmailSerializer classes
+### Changed
+- All getters and setter in BasicField are now declared final
+- FormWithFields accepts only ContainerInterface in ::set_data() method. Prepare data before passing it
+### Removed
+- Removed deprecated classes
+- Removed NoSerialize class
+
 ## [2.4.12] - 2021-12-22
 ### Fixed
 - Fixed image input JS
diff --git a/composer.json b/composer.json
index 45237352e32137aa83c1e52ddaad17daf2c486bd..03b48b4c3c4904c3545fedbd94b7cb7f6ff56239 100644
--- a/composer.json
+++ b/composer.json
@@ -1,51 +1,53 @@
 {
-  "name": "wpdesk/wp-forms",
-  "description": "WP Forms",
-  "authors": [
-    {
-      "name": "Marcin",
-      "email": "marcin@wpdesk.pl"
-    },
-    {
-      "name": "Krzysiek",
-      "email": "krzysiek@wpdesk.pl"
-    },
-    {
-      "name": "Piotr Potrebka",
-      "email": "piotr.potrebka@wpdesk.pl"
-    }
-  ],
-  "require": {
-    "php": ">=5.6",
-    "ext-curl": "*",
-    "ext-json": "*",
-    "wpdesk/wp-persistence": "^2.0|^3.0",
-    "wpdesk/wp-view": "^2"
-  },
-  "require-dev": {
-    "phpunit/phpunit": "<7",
-    "mockery/mockery": "*",
-    "10up/wp_mock": "*",
-		"wpdesk/wp-code-sniffer": "^1.2.3"
-  },
-  "autoload": {
-    "psr-4": {
-      "WPDesk\\Forms\\": "src/"
-    }
-  },
-  "autoload-dev": {
-  },
-  "extra": {
-    "text-domain": "wp-forms",
-    "translations-folder": "lang",
-    "po-files": {
-      "pl_PL": "pl_PL.po"
-    }
-  },
-  "scripts": {
-    "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",
-    "phpunit-integration-fast": "phpunit --configuration phpunit-integration.xml --no-coverage"
-  }
+	"name": "wpdesk/wp-forms",
+	"description": "WP Forms",
+	"authors": [
+		{
+			"name": "Marcin",
+			"email": "marcin@wpdesk.pl"
+		},
+		{
+			"name": "Krzysiek",
+			"email": "krzysiek@wpdesk.pl"
+		},
+		{
+			"name": "Piotr Potrebka",
+			"email": "piotr.potrebka@wpdesk.pl"
+		}
+	],
+	"require": {
+		"php": ">=7.0",
+		"ext-curl": "*",
+		"ext-json": "*",
+		"wpdesk/wp-persistence": "^2.0|^3.0",
+		"wpdesk/wp-view": "^2"
+	},
+	"require-dev": {
+		"phpunit/phpunit": "<7",
+		"phpstan/phpstan": "^0.12",
+		"wpdesk/wp-code-sniffer": "^1.2.3",
+		"10up/wp_mock": "*",
+		"szepeviktor/phpstan-wordpress": "^0.7.7",
+		"phpstan/extension-installer": "^1.1"
+	},
+	"autoload": {
+		"psr-4": {
+			"WPDesk\\Forms\\": "src/"
+		}
+	},
+	"autoload-dev": {
+	},
+	"extra": {
+		"text-domain": "wp-forms",
+		"translations-folder": "lang",
+		"po-files": {
+			"pl_PL": "pl_PL.po"
+		}
+	},
+	"scripts": {
+		"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",
+		"phpunit-integration-fast": "phpunit --configuration phpunit-integration.xml --no-coverage"
+	}
 }
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
new file mode 100644
index 0000000000000000000000000000000000000000..200d200a6b18db3d5abbcd4e9ee9edbd96924c25
--- /dev/null
+++ b/phpstan.neon.dist
@@ -0,0 +1,4 @@
+parameters:
+    level: max
+    paths:
+        - src/
diff --git a/phpunit-integration.xml b/phpunit-integration.xml
index 4a342abf125d638d044bafa01cd9a75a771b4b3d..8341e36c040a27097cd60e3e4d3330f7c5da9298 100644
--- a/phpunit-integration.xml
+++ b/phpunit-integration.xml
@@ -3,7 +3,7 @@
      >
     <testsuites>
         <testsuite>
-            <directory prefix="Test" suffix=".php">./tests/integration</directory>
+            <directory suffix="Test.php">./tests/integration</directory>
         </testsuite>
     </testsuites>
 
@@ -13,16 +13,5 @@
         </whitelist>
     </filter>
 
-    <logging>
-        <log type="junit" target="build-coverage/report.junit.xml"/>
-        <log type="coverage-html" target="build-coverage/coverage" charset="UTF-8" yui="true" highlight="true"/>
-        <log type="coverage-text" target="build-coverage/coverage.txt"/>
-        <log type="coverage-clover" target="build-coverage/clover.xml"/>
-    </logging>
-
-    <php>
-        <env name="WP_DEVELOP_DIR" value="/tmp/wordpress-develop"/>
-        <env name="WC_DEVELOP_DIR" value="/tmp/woocommerce"/>
-    </php>
 
 </phpunit>
\ No newline at end of file
diff --git a/src/ContainerForm.php b/src/ContainerForm.php
index 1d056b6cacd598007ecc177b29be31b4d7770ed0..e3b6dde63d31bf31e483b37b6a3c69de2012a3f7 100644
--- a/src/ContainerForm.php
+++ b/src/ContainerForm.php
@@ -2,7 +2,6 @@
 
 namespace WPDesk\Forms;
 
-
 use Psr\Container\ContainerInterface;
 use WPDesk\Persistence\PersistentContainer;
 
@@ -12,18 +11,17 @@ use WPDesk\Persistence\PersistentContainer;
  * @package WPDesk\Forms
  */
 interface ContainerForm {
+
 	/**
 	 * @param ContainerInterface $data
 	 *
 	 * @return void
 	 */
-	public function set_data( $data );
+	public function set_data( ContainerInterface $data );
 
 	/**
 	 * Put data from form into a container.
 	 *
-	 * @param PersistentContainer $container Target container.
-	 *
 	 * @return void
 	 */
 	public function put_data( PersistentContainer $container );
diff --git a/src/Escaper.php b/src/Escaper.php
index cb20d5824a44f3890c8c48007c9b0d6bb04be1c4..462295c4515f0adc3321ae31f4fa0b19050768b7 100644
--- a/src/Escaper.php
+++ b/src/Escaper.php
@@ -3,10 +3,7 @@
 namespace WPDesk\Forms;
 
 interface Escaper {
-	/**
-	 * @param mixed $value
-	 *
-	 * @return string
-	 */
-	public function escape( $value );
+
+	/** @param mixed $value */
+	public function escape( $value ): string;
 }
diff --git a/src/Field.php b/src/Field.php
index 458bb386815097e0262bff46d4c8fcbdd0fd1f03..b932f273f01a7dff40ffeedd12be722d943e0869 100644
--- a/src/Field.php
+++ b/src/Field.php
@@ -2,8 +2,6 @@
 
 namespace WPDesk\Forms;
 
-use WPDesk\Forms\Field\BasicField;
-
 /**
  * The idea is that from the moment the factory returns this interface it's values cannot be changed.
  * And that is why here are only the getters.
@@ -13,154 +11,88 @@ use WPDesk\Forms\Field\BasicField;
  * @package WPDesk\Forms
  */
 interface Field {
-	/** @return string */
-	public function get_name();
+
+	public function get_name(): string;
 
 	/** @return mixed */
 	public function get_default_value();
 
-	/** @return string */
-	public function get_template_name();
+	public function get_template_name(): string;
 
-	/**
-	 * When this field is used on form this field will force it's own template.
-	 *
-	 * return bool
-	 */
-	public function should_override_form_template();
+	/** When this field is used on form this field will force it's own template. */
+	public function should_override_form_template(): bool;
 
-	/**
-	 * HTML label.
-	 *
-	 * @return string
-	 */
-	public function get_label();
+	/** HTML label. */
+	public function get_label(): string;
 
-	/** bool */
-	public function has_label();
+	public function has_label(): bool;
 
-	/**
-	 * Description for field. It can be shown near the field.
-	 *
-	 * @return string
-	 */
-	public function get_description();
+	public function get_description(): string;
 
-	/**
-	 * Additional field description that should be shown in optional hover tip.
-	 *
-	 * @return string
-	 */
-	public function get_description_tip();
+	/** Additional field description that should be shown in optional hover tip. */
+	public function get_description_tip(): string;
 
-	/** @return bool */
-	public function has_description_tip();
+	public function has_description_tip(): bool;
 
-	/** @return bool */
-	public function has_description();
+	public function has_description(): bool;
 
-	/**
-	 * @return bool
-	 */
-	public function is_readonly();
+	public function is_readonly(): bool;
 
-	/** @return bool */
-	public function is_disabled();
+	public function is_disabled(): bool;
 
-	/** @return string */
-	public function get_id();
+	public function get_id(): string;
 
-	/** @bool */
-	public function is_required();
+	public function is_required(): bool;
 
-	/** @return bool */
-	public function has_placeholder();
+	public function has_placeholder(): bool;
 
-	/** @return string */
-	public function get_placeholder();
+	public function get_placeholder(): string;
 
 	/**
 	 * @param string[] $except
 	 *
 	 * @return string[] name->value
 	 */
-	public function get_attributes( $except = [] );
+	public function get_attributes( array $except = [] ): array;
 
-	/**
-	 * @param string $name
-	 * @param string $default
-	 *
-	 * @return string
-	 */
-	public function get_attribute( $name, $default = null );
+	public function get_attribute( string $name, string $default = null ): string;
 
-	/** @return bool */
-	public function is_attribute_set( $name );
+	public function is_attribute_set( string $name ): bool;
 
-	/**
-	 * @param string $name
-	 *
-	 * @return string
-	 */
-	public function get_meta_value( $name );
+	/** @return mixed */
+	public function get_meta_value( string $name );
 
-	/** @return bool */
-	public function is_meta_value_set( $name );
+	public function is_meta_value_set( string $name ): bool;
 
-	/**
-	 * @return string
-	 */
-	public function get_classes();
+	public function get_classes(): string;
 
-	/** bool */
-	public function has_classes();
+	public function get_type(): string;
 
-	/** @return bool */
-	public function is_class_set( $name );
+	public function has_classes(): bool;
 
-	/** bool */
-	public function has_data();
+	public function is_class_set( string $name ): bool;
 
-	/**
-	 * @return array
-	 */
-	public function get_data();
+	public function has_data(): bool;
 
-	/**
-	 * @param string $data_name
-	 * @param string $data_value
-	 *
-	 * @return $this
-	 */
-	public function add_data( $data_name, $data_value );
+	/** @return array<string|int> */
+	public function get_data(): array;
 
-	/**
-	 * @param string $data_name
-	 *
-	 * @return $this
-	 */
-	public function unset_data( $data_name );
+	public function add_data( string $data_name, string $data_value ): Field;
 
-	/**
-	 * @return mixed
-	 */
+	public function unset_data( string $data_name ): Field;
+
+	/** @return mixed */
 	public function get_possible_values();
 
-	/**
-	 * @return bool
-	 */
-	public function is_multiple();
+	public function is_multiple(): bool;
 
-	/**
-	 * @return Validator
-	 */
-	public function get_validator();
+	public function get_validator(): Validator;
 
-	/**
-	 * @return Sanitizer
-	 */
-	public function get_sanitizer();
+	public function get_sanitizer(): Sanitizer;
+
+	public function get_serializer(): Serializer;
+
+	public function has_serializer(): bool;
 
-	/** @return Serializer */
-	public function get_serializer();
+	public function get_priority(): int;
 }
diff --git a/src/Field/BasicField.php b/src/Field/BasicField.php
index da939228abaf3687dd3e1f41a6bfb96efa121b2c..3e34ff9508edc1367947ebd479c7aad8e93e5a99 100644
--- a/src/Field/BasicField.php
+++ b/src/Field/BasicField.php
@@ -2,225 +2,209 @@
 
 namespace WPDesk\Forms\Field;
 
+use BadMethodCallException;
 use WPDesk\Forms\Field;
+use WPDesk\Forms\Sanitizer;
 use WPDesk\Forms\Sanitizer\NoSanitize;
 use WPDesk\Forms\Serializer;
-use WPDesk\Forms\Serializer\NoSerialize;
+use WPDesk\Forms\Validator;
 use WPDesk\Forms\Validator\ChainValidator;
 use WPDesk\Forms\Validator\RequiredValidator;
 
 /**
  * Base class for fields. Is responsible for settings all required field values and provides standard implementation for
  * the field interface.
- *
- * @package WPDesk\Forms
  */
 abstract class BasicField implements Field {
+
 	use Field\Traits\HtmlAttributes;
 
-	/** @var array[] */
-	protected $meta;
+	const DEFAULT_PRIORITY = 10;
 
-	protected $default_value;
+	/** @var array{default_value: string, possible_values?: string[], sublabel?: string, priority: int, label: string, description: string, description_tip: string, data: array<string|int>} */
+	protected $meta = [
+		'priority'        => self::DEFAULT_PRIORITY,
+		'default_value'   => '',
+		'label'           => '',
+		'description'     => '',
+		'description_tip' => '',
+		'data'            => [],
+		'type'            => 'text',
+	];
 
-	public function __construct() {
-		$this->meta['class'] = [];
+	public function should_override_form_template(): bool {
+		return false;
 	}
 
-	public function get_label() {
-		return $this->meta['label'];
+	public function get_type(): string {
+		return $this->meta['type'];
 	}
 
-	/**
-	 * @param string $value
-	 *
-	 * @return $this
-	 */
-	public function set_label( $value ) {
-		$this->meta['label'] = $value;
+	public function set_type( string $type ): Field {
+		$this->meta['type'] = $type;
 
 		return $this;
 	}
 
-	public function get_description_tip() {
-		return $this->meta['description_tip'];
+	public function get_validator(): Validator {
+		$chain = new ChainValidator();
+		if ( $this->is_required() ) {
+			$chain->attach( new RequiredValidator() );
+		}
+
+		return $chain;
 	}
 
-	public function has_description_tip() {
-		return isset( $this->meta['description_tip'] );
+	public function get_sanitizer(): Sanitizer {
+		return new NoSanitize();
 	}
 
-	public function should_override_form_template() {
-		return isset( $this->attributes['overrite_template'] ) ? $this->attributes['overrite_template'] : false;
+	public function has_serializer(): bool {
+		return false;
 	}
 
-	public function get_description() {
-		return $this->meta['description'];
+	public function get_serializer(): Serializer {
+		throw new BadMethodCallException( 'You must define your serializer in a child class.' );
 	}
 
-	public function has_label() {
-		return isset( $this->meta['label'] );
+	final public function get_name(): string {
+		return $this->attributes['name'] ?? '';
 	}
 
-	public function has_description() {
-		return isset( $this->meta['description'] );
+	final public function get_label(): string {
+		return $this->meta['label'] ?? '';
 	}
 
-	public function set_description( $value ) {
-		$this->meta['description'] = $value;
+	final public function set_label( string $value ): Field {
+		$this->meta['label'] = $value;
 
 		return $this;
 	}
 
-	public function set_description_tip( $value ) {
-		$this->meta['description_tip'] = $value;
+	final public function get_description_tip(): string {
+		return $this->meta['description_tip'] ?? '';
+	}
+
+	final public function has_description_tip(): bool {
+		return ! empty( $this->meta['description_tip'] );
+	}
+
+	final public function get_description(): string {
+		return $this->meta['description'] ?? '';
+	}
+
+	final public function has_label(): bool {
+		return ! empty( $this->meta['label'] );
+	}
+
+	final public function has_description(): bool {
+		return ! empty( $this->meta['description'] );
+	}
+
+	final public function set_description( string $value ): Field {
+		$this->meta['description'] = $value;
 
 		return $this;
 	}
 
-	/**
-	 * @return array
-	 *
-	 * @deprecated not sure if needed. TODO: Check later.
-	 */
-	public function get_type() {
-		return $this->attributes['type'];
+	final public function set_description_tip( string $value ): Field {
+		$this->meta['description_tip'] = $value;
+
+		return $this;
 	}
 
-	/**
-	 * @param string $value
-	 *
-	 * @return $this
-	 */
-	public function set_placeholder( $value ) {
-		$this->meta['placeholder'] = $value;
+	final public function set_placeholder( string $value ): Field {
+		$this->attributes['placeholder'] = $value;
 
 		return $this;
 	}
 
-	public function has_placeholder() {
-		return isset( $this->meta['placeholder'] );
+	final public function has_placeholder(): bool {
+		return ! empty( $this->attributes['placeholder'] );
 	}
 
-	public function get_placeholder() {
-		return $this->meta['placeholder'];
+	final public function get_placeholder(): string {
+		return $this->attributes['placeholder'] ?? '';
 	}
 
-	/**
-	 * @param string $name
-	 *
-	 * @return $this
-	 */
-	public function set_name( $name ) {
+	final public function set_name( string $name ): Field {
 		$this->attributes['name'] = $name;
 
 		return $this;
 	}
 
-	public function get_meta_value( $name ) {
-		return $this->meta[ $name ];
+	final public function get_meta_value( string $name ) {
+		return $this->meta[ $name ] ?? '';
 	}
 
-	public function get_classes() {
-		return implode( ' ', $this->meta['class'] );
+	final public function get_classes(): string {
+		return implode( ' ', $this->attributes['class'] ?? [] );
 	}
 
-	public function has_classes() {
-		return ! empty( $this->meta['class'] );
+	final public function has_classes(): bool {
+		return ! empty( $this->attributes['class'] );
 	}
 
-	public function has_data() {
+	final public function has_data(): bool {
 		return ! empty( $this->meta['data'] );
 	}
 
-	/**
-	 * @return array
-	 */
-	public function get_data() {
-		return empty( $this->meta['data'] ) ? [] : $this->meta['data'];
+	final public function get_data(): array {
+		return $this->meta['data'] ?? [];
 	}
 
-	public function get_possible_values() {
-		return isset( $this->meta['possible_values'] ) ? $this->meta['possible_values'] : [];
+	final public function get_possible_values() {
+		return ! empty( $this->meta['possible_values'] ) ? $this->meta['possible_values'] : [];
 	}
 
-	public function get_id() {
-		return isset( $this->attributes['id'] ) ? $this->attributes['id'] : sanitize_title( $this->get_name() );
+	final public function get_id(): string {
+		return $this->attributes['id'] ?? sanitize_title( $this->get_name() );
 	}
 
-	public function get_name() {
-		return $this->attributes['name'];
+	final public function is_multiple(): bool {
+		return isset( $this->attributes['multiple'] );
 	}
 
-	public function is_multiple() {
-		return isset( $this->attributes['multiple'] ) ? $this->attributes['multiple'] : false;
-	}
-
-	/**
-	 * @return $this
-	 */
-	public function set_disabled() {
-		$this->attributes['disabled'] = true;
+	final public function set_disabled(): Field {
+		$this->attributes['disabled'] = 'disabled';
 
 		return $this;
 	}
 
-	public function is_disabled() {
-		return isset( $this->attributes['disabled'] ) ? $this->attributes['disabled'] : false;
+	final public function is_disabled(): bool {
+		return $this->attributes['disabled'] ?? false;
 	}
 
-	/**
-	 * @return $this
-	 */
-	public function set_readonly() {
-		$this->attributes['readonly'] = true;
+	final public function set_readonly(): Field {
+		$this->attributes['readonly'] = 'readonly';
 
 		return $this;
 	}
 
-	public function is_readonly() {
-		return isset( $this->attributes['readonly'] ) ? $this->attributes['readonly'] : false;
+	final public function is_readonly(): bool {
+		return $this->attributes['readonly'] ?? false;
 	}
 
-	/**
-	 * @return $this
-	 */
-	public function set_required() {
-		$this->meta['required'] = true;
+	final public function set_required(): Field {
+		$this->attributes['required'] = 'required';
 
 		return $this;
 	}
 
-	/**
-	 * @param string $class_name
-	 *
-	 * @return $this
-	 */
-	public function add_class( $class_name ) {
-		$this->meta['class'][ $class_name ] = $class_name;
+	final public function add_class( string $class_name ): Field {
+		$this->attributes['class'][ $class_name ] = $class_name;
 
 		return $this;
 	}
 
-	/**
-	 * @param string $class_name
-	 *
-	 * @return $this
-	 */
-	public function unset_class( $class_name ) {
-		unset( $this->meta['class'][ $class_name ] );
+	final public function unset_class( string $class_name ): Field {
+		unset( $this->attributes['class'][ $class_name ] );
 
 		return $this;
 	}
 
-	/**
-	 * @param string $data_name
-	 * @param string $data_value
-	 *
-	 * @return $this
-	 */
-	public function add_data( $data_name, $data_value ) {
-		if ( !isset( $this->meta['data'] ) ) {
+	final public function add_data( string $data_name, string $data_value ): Field {
+		if ( empty( $this->meta['data'] ) ) {
 			$this->meta['data'] = [];
 		}
 		$this->meta['data'][ $data_name ] = $data_value;
@@ -228,73 +212,45 @@ abstract class BasicField implements Field {
 		return $this;
 	}
 
-	/**
-	 * @param string $data_name
-	 *
-	 * @return $this
-	 */
-	public function unset_data( $data_name ) {
+	final public function unset_data( string $data_name ): Field {
 		unset( $this->meta['data'][ $data_name ] );
 
 		return $this;
 	}
 
-	public function is_meta_value_set( $name ) {
-		return isset( $this->meta[ $name ] );
+	final public function is_meta_value_set( string $name ): bool {
+		return ! empty( $this->meta[ $name ] );
 	}
 
-	public function is_class_set( $name ) {
-		return isset( $this->meta['class'][ $name ] );
+	final public function is_class_set( string $name ): bool {
+		return ! empty( $this->attributes['class'][ $name ] );
 	}
 
-	public function get_default_value() {
-		return $this->default_value;
+	final public function get_default_value(): string {
+		return $this->meta['default_value'] ?? '';
 	}
 
-	/**
-	 * @param string $value
-	 *
-	 * @return $this
-	 */
-	public function set_default_value( $value ) {
-		$this->default_value = $value;
+	final public function set_default_value( string $value ): Field {
+		$this->meta['default_value'] = $value;
 
 		return $this;
 	}
 
-	/**
-	 * @return ChainValidator
-	 */
-	public function get_validator() {
-		$chain = new ChainValidator();
-		if ( $this->is_required() ) {
-			$chain->attach( new RequiredValidator() );
-		}
-
-		return $chain;
+	final public function is_required(): bool {
+		return isset( $this->attributes['required'] );
 	}
 
-	public function is_required() {
-		return isset( $this->meta['required'] ) ? $this->meta['required'] : false;
-	}
-
-	public function get_sanitizer() {
-		return new NoSanitize();
+	final public function get_priority(): int {
+		return $this->meta['priority'];
 	}
 
 	/**
-	 * @return Serializer
+	 * Fields are sorted by lowest priority value first, when getting FormWithFields
+	 *
+	 * @see FormWithFields::get_fields()
 	 */
-	public function get_serializer() {
-		if ( isset( $this->meta['serializer'] ) && $this->meta['serializer'] instanceof Serializer ) {
-			return $this->meta['serializer'];
-		}
-
-		return new NoSerialize();
-	}
-
-	public function set_serializer( Serializer $serializer ) {
-		$this->meta['serializer'] = $serializer;
+	final public function set_priority( int $priority ): Field {
+		$this->meta['priority'] = $priority;
 
 		return $this;
 	}
diff --git a/src/Field/ButtonField.php b/src/Field/ButtonField.php
index 86312d03a1bab763609441410786f901be1290c7..1a43c3ff81524108364c1f240dd2337059a9510c 100644
--- a/src/Field/ButtonField.php
+++ b/src/Field/ButtonField.php
@@ -2,14 +2,13 @@
 
 namespace WPDesk\Forms\Field;
 
-class ButtonField extends NoValueField
-{
-    public function get_template_name()
-    {
-        return 'button';
-    }
-    public function get_type()
-    {
-        return 'button';
-    }
+class ButtonField extends NoValueField {
+
+	public function get_template_name(): string {
+		return 'button';
+	}
+
+	public function get_type(): string {
+		return 'button';
+	}
 }
diff --git a/src/Field/CheckboxField.php b/src/Field/CheckboxField.php
index 705c4fe2f97e7f895ef1c58b4534c1465357a2ee..14110296cb1f03405c8928913d9fd1a6ac1b4a00 100644
--- a/src/Field/CheckboxField.php
+++ b/src/Field/CheckboxField.php
@@ -2,31 +2,32 @@
 
 namespace WPDesk\Forms\Field;
 
+use WPDesk\Forms\Field;
 
 class CheckboxField extends BasicField {
-	const VALUE_TRUE = 'yes';
+
+	const VALUE_TRUE  = 'yes';
 	const VALUE_FALSE = 'no';
 
-	public function __construct() {
-		parent::__construct();
-		$this->set_attribute( 'type', 'checkbox' );
+	public function get_type(): string {
+		return 'checkbox';
 	}
 
-	public function get_template_name() {
+	public function get_template_name(): string {
 		return 'input-checkbox';
 	}
 
-	public function get_sublabel() {
+	public function get_sublabel(): string {
 		return $this->meta['sublabel'];
 	}
 
-	public function set_sublabel( $value ) {
+	public function set_sublabel( string $value ): Field {
 		$this->meta['sublabel'] = $value;
 
 		return $this;
 	}
 
-	public function has_sublabel() {
+	public function has_sublabel(): bool {
 		return isset( $this->meta['sublabel'] );
 	}
 }
diff --git a/src/Field/DatePickerField.php b/src/Field/DatePickerField.php
index 8b50df8927a706972fa740cb61f19ce7af7eb0dd..2ec2160afab95c3475dca1eed2d3682f988a9b01 100644
--- a/src/Field/DatePickerField.php
+++ b/src/Field/DatePickerField.php
@@ -2,22 +2,21 @@
 
 namespace WPDesk\Forms\Field;
 
+use WPDesk\Forms\Sanitizer;
 use WPDesk\Forms\Sanitizer\TextFieldSanitizer;
 
 class DatePickerField extends BasicField {
+
 	public function __construct() {
-		parent::__construct();
-		$this->set_default_value( '' );
-		$this->add_class('date-picker');
-		$this->set_placeholder('YYYY-MM-DD');
-		$this->set_attribute( 'type', 'text' );
+		$this->add_class( 'date-picker' );
+		$this->set_placeholder( 'YYYY-MM-DD' );
 	}
 
-	public function get_sanitizer() {
+	public function get_sanitizer(): Sanitizer {
 		return new TextFieldSanitizer();
 	}
 
-	public function get_template_name() {
+	public function get_template_name(): string {
 		return 'input-date-picker';
 	}
 }
diff --git a/src/Field/Header.php b/src/Field/Header.php
index 491d99c9e86981ff619da27f21786cd2830ee11e..b588b8f3362872393e8ec50316829a1cd9343ca6 100644
--- a/src/Field/Header.php
+++ b/src/Field/Header.php
@@ -2,21 +2,24 @@
 
 namespace WPDesk\Forms\Field;
 
+use WPDesk\Forms\Field;
+
 class Header extends NoValueField {
+
 	public function __construct() {
 		parent::__construct();
 		$this->meta['header_size'] = '';
 	}
 
-	public function get_template_name() {
+	public function get_template_name(): string {
 		return 'header';
 	}
 
-	public function should_override_form_template() {
+	public function should_override_form_template(): bool {
 		return true;
 	}
 
-	public function set_header_size( $value ) {
+	public function set_header_size( int $value ): Field {
 		$this->meta['header_size'] = $value;
 
 		return $this;
diff --git a/src/Field/HiddenField.php b/src/Field/HiddenField.php
index 51b78080d7e9533b28f8ea54c362158971633223..ff06bdc0dc4044a92c69e88bd0c03abe361ed7e2 100644
--- a/src/Field/HiddenField.php
+++ b/src/Field/HiddenField.php
@@ -2,20 +2,20 @@
 
 namespace WPDesk\Forms\Field;
 
+use WPDesk\Forms\Sanitizer;
 use WPDesk\Forms\Sanitizer\TextFieldSanitizer;
 
 class HiddenField extends BasicField {
-	public function __construct() {
-		parent::__construct();
-		$this->set_default_value( '' );
-		$this->set_attribute( 'type', 'hidden' );
+
+	public function get_type(): string {
+		return 'hidden';
 	}
 
-	public function get_sanitizer() {
+	public function get_sanitizer(): Sanitizer {
 		return new TextFieldSanitizer();
 	}
 
-	public function get_template_name() {
+	public function get_template_name(): string {
 		return 'input-hidden';
 	}
 }
diff --git a/src/Field/ImageInputField.php b/src/Field/ImageInputField.php
index 95d4bb68f3492b3139f2ebb56a3d4c379ad4d5c0..1f58ec50e39ba2b49c5e21500452786ea09a96f5 100644
--- a/src/Field/ImageInputField.php
+++ b/src/Field/ImageInputField.php
@@ -4,16 +4,7 @@ namespace WPDesk\Forms\Field;
 
 class ImageInputField extends BasicField {
 
-	public function __construct() {
-		parent::__construct();
-		$this->set_default_value( '' );
-		$this->set_attribute( 'type', 'text' );
-	}
-
-	/**
-	 * @return string
-	 */
-	public function get_template_name() {
+	public function get_template_name(): string {
 		return 'input-image';
 	}
 }
diff --git a/src/Field/InputEmailField.php b/src/Field/InputEmailField.php
new file mode 100644
index 0000000000000000000000000000000000000000..36325f3fd9fcd61472448300199d8d64813bc9bd
--- /dev/null
+++ b/src/Field/InputEmailField.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace WPDesk\Forms\Field;
+
+use WPDesk\Forms\Sanitizer;
+use WPDesk\Forms\Sanitizer\EmailSanitizer;
+
+class InputEmailField extends BasicField {
+
+	public function get_type(): string {
+		return 'email';
+	}
+
+	public function get_sanitizer(): Sanitizer {
+		return new EmailSanitizer();
+	}
+
+	public function get_template_name(): string {
+		return 'input-text';
+	}
+}
diff --git a/src/Field/InputNumberField.php b/src/Field/InputNumberField.php
index a563cd29fe80b418c0e1aee195883d512300b773..84aac99e37786d2729f0c4826c4b113002c50b7e 100644
--- a/src/Field/InputNumberField.php
+++ b/src/Field/InputNumberField.php
@@ -2,20 +2,20 @@
 
 namespace WPDesk\Forms\Field;
 
+use WPDesk\Forms\Sanitizer;
 use WPDesk\Forms\Sanitizer\TextFieldSanitizer;
 
 class InputNumberField extends BasicField {
-	public function __construct() {
-		parent::__construct();
-		$this->set_default_value( '' );
-		$this->set_attribute( 'type', 'number' );
+
+	public function get_type(): string {
+		return 'number';
 	}
 
-	public function get_sanitizer() {
+	public function get_sanitizer(): Sanitizer {
 		return new TextFieldSanitizer();
 	}
 
-	public function get_template_name() {
+	public function get_template_name(): string {
 		return 'input-number';
 	}
 }
diff --git a/src/Field/InputTextField.php b/src/Field/InputTextField.php
index 2070412379ea091eeb87a4ca9b3994bbe7acaf9b..92f473d24a7fad512ba5866e77442c77914eee67 100644
--- a/src/Field/InputTextField.php
+++ b/src/Field/InputTextField.php
@@ -2,20 +2,16 @@
 
 namespace WPDesk\Forms\Field;
 
+use WPDesk\Forms\Sanitizer;
 use WPDesk\Forms\Sanitizer\TextFieldSanitizer;
 
 class InputTextField extends BasicField {
-	public function __construct() {
-		parent::__construct();
-		$this->set_default_value( '' );
-		$this->set_attribute( 'type', 'text' );
-	}
 
-	public function get_sanitizer() {
+	public function get_sanitizer(): Sanitizer {
 		return new TextFieldSanitizer();
 	}
 
-	public function get_template_name() {
+	public function get_template_name(): string {
 		return 'input-text';
 	}
 }
diff --git a/src/Field/MultipleInputTextField.php b/src/Field/MultipleInputTextField.php
index fab83989cda40645ab355f631dc7ec1665c61185..cf976fe79f6f1309984fd24886a4187bd76b5cf1 100644
--- a/src/Field/MultipleInputTextField.php
+++ b/src/Field/MultipleInputTextField.php
@@ -4,10 +4,7 @@ namespace WPDesk\Forms\Field;
 
 class MultipleInputTextField extends InputTextField {
 
-	/**
-	 * @return string
-	 */
-	public function get_template_name() {
+	public function get_template_name(): string {
 		return 'input-text-multiple';
 	}
 }
diff --git a/src/Field/NoOnceField.php b/src/Field/NoOnceField.php
index 0b6eeafd10f30358cfd7f29e619a1b9d6fa28d7f..17ac6e05315d442399a6f38148879f045309fb49 100644
--- a/src/Field/NoOnceField.php
+++ b/src/Field/NoOnceField.php
@@ -2,20 +2,20 @@
 
 namespace WPDesk\Forms\Field;
 
+use WPDesk\Forms\Validator;
 use WPDesk\Forms\Validator\NonceValidator;
 
 class NoOnceField extends BasicField {
 
-	public function __construct( $action_name ) {
-		parent::__construct();
+	public function __construct( string $action_name ) {
 		$this->meta['action'] = $action_name;
 	}
 
-	public function get_validator() {
+	public function get_validator(): Validator {
 		return new NonceValidator( $this->get_meta_value( 'action' ) );
 	}
 
-	public function get_template_name() {
+	public function get_template_name(): string {
 		return 'noonce';
 	}
 }
diff --git a/src/Field/NoValueField.php b/src/Field/NoValueField.php
index 830f47ef4fda51d95d0260afe23e3f232d0ef4e2..a84a1a7ad56664f423f9e4861293a5a2edb7103b 100644
--- a/src/Field/NoValueField.php
+++ b/src/Field/NoValueField.php
@@ -8,7 +8,8 @@ namespace WPDesk\Forms\Field;
  * @package WPDesk\Forms
  */
 abstract class NoValueField extends BasicField {
-	public function get_name() {
-		return '';
+
+	public function __construct() {
+		$this->set_name( '' );
 	}
 }
diff --git a/src/Field/Paragraph.php b/src/Field/Paragraph.php
index c62cb6b844d1846636e8aeaff8d2e1b69798ac03..253eea02338e56780ff33b1c64c4d2d2124ede23 100644
--- a/src/Field/Paragraph.php
+++ b/src/Field/Paragraph.php
@@ -3,11 +3,12 @@
 namespace WPDesk\Forms\Field;
 
 class Paragraph extends NoValueField {
-	public function get_template_name() {
+
+	public function get_template_name(): string {
 		return 'paragraph';
 	}
 
-	public function should_override_form_template() {
+	public function should_override_form_template(): bool {
 		return true;
 	}
 }
diff --git a/src/Field/ProductSelect.php b/src/Field/ProductSelect.php
index 19b14aebc1223e6db73bf34a1a1811ce7560aa3c..ff14f8eb1b7b218588bf2dde2fb9538a1bdcc04e 100644
--- a/src/Field/ProductSelect.php
+++ b/src/Field/ProductSelect.php
@@ -2,13 +2,24 @@
 
 namespace WPDesk\Forms\Field;
 
+use WPDesk\Forms\Serializer\ProductSelectSerializer;
+use WPDesk\Forms\Serializer;
+
 class ProductSelect extends SelectField {
+
 	public function __construct() {
-		parent::__construct();
 		$this->set_multiple();
 	}
 
-	public function get_template_name() {
+	public function has_serializer(): bool {
+		return true;
+	}
+
+	public function get_serializer(): Serializer {
+		return new ProductSelectSerializer();
+	}
+
+	public function get_template_name(): string {
 		return 'product-select';
 	}
 }
diff --git a/src/Field/RadioField.php b/src/Field/RadioField.php
index 69630bf430c7ba352948f44dedb8b340b5df67e4..73c5f7084ace90812316a74b3e17839cfe347b66 100644
--- a/src/Field/RadioField.php
+++ b/src/Field/RadioField.php
@@ -3,7 +3,12 @@
 namespace WPDesk\Forms\Field;
 
 class RadioField extends BasicField {
-	public function get_template_name() {
+
+	public function get_type(): string {
+		return 'radio';
+	}
+
+	public function get_template_name(): string {
 		return 'input-radio';
 	}
 }
diff --git a/src/Field/SelectField.php b/src/Field/SelectField.php
index 0578379dc7d68c781025955f815eee3018db3d47..04d290cfe922ab32cc7979f51ebf9c3138e3b730 100644
--- a/src/Field/SelectField.php
+++ b/src/Field/SelectField.php
@@ -2,20 +2,27 @@
 
 namespace WPDesk\Forms\Field;
 
+use WPDesk\Forms\Field;
+
 class SelectField extends BasicField {
 
-	public function get_template_name() {
+	public function get_type(): string {
+		return 'select';
+	}
+
+	public function get_template_name(): string {
 		return 'select';
 	}
 
-	public function set_options( $options ) {
+	/** @param string[] $options */
+	public function set_options( array $options ): Field {
 		$this->meta['possible_values'] = $options;
 
 		return $this;
 	}
 
-	public function set_multiple() {
-		$this->attributes['multiple'] = true;
+	public function set_multiple(): Field {
+		$this->attributes['multiple'] = 'multiple';
 
 		return $this;
 	}
diff --git a/src/Field/SubmitField.php b/src/Field/SubmitField.php
index c8e1f799d895ae49a10712439043a2b0e6a223e5..0decb84d2695c2a820c04aab7d54a3c8cfecb084 100644
--- a/src/Field/SubmitField.php
+++ b/src/Field/SubmitField.php
@@ -3,15 +3,16 @@
 namespace WPDesk\Forms\Field;
 
 class SubmitField extends NoValueField {
-	public function get_template_name() {
+
+	public function get_template_name(): string {
 		return 'input-submit';
 	}
 
-	public function get_type() {
+	public function get_type(): string {
 		return 'submit';
 	}
 
-	public function should_override_form_template() {
+	public function should_override_form_template(): bool {
 		return true;
 	}
 }
diff --git a/src/Field/TextAreaField.php b/src/Field/TextAreaField.php
index 94873b3f0b07dfa0f1a2ea033da60c69b92607b2..81ed137d0afa1149a69a88a17075a7ddc020a90e 100644
--- a/src/Field/TextAreaField.php
+++ b/src/Field/TextAreaField.php
@@ -3,12 +3,12 @@
 namespace WPDesk\Forms\Field;
 
 class TextAreaField extends BasicField {
-	public function __construct() {
-		parent::__construct();
-		$this->set_default_value( '' );
+
+	public function get_type(): string {
+		return 'textarea';
 	}
 
-	public function get_template_name() {
+	public function get_template_name(): string {
 		return 'textarea';
 	}
 }
diff --git a/src/Field/TimepickerField.php b/src/Field/TimepickerField.php
index 55b1fb12c4d75af64163605303092e0819759beb..f1bf91aae3dde56c7a855370acc2ba6517746854 100644
--- a/src/Field/TimepickerField.php
+++ b/src/Field/TimepickerField.php
@@ -2,11 +2,24 @@
 
 namespace WPDesk\Forms\Field;
 
+use WPDesk\Forms\Serializer;
+use WPDesk\Forms\Serializer\JsonSerializer;
+
 class TimepickerField extends BasicField {
-	/**
-	 * @inheritDoc
-	 */
-	public function get_template_name() {
+
+	public function get_type(): string {
+		return 'time';
+	}
+
+	public function has_serializer(): bool {
+		return true;
+	}
+
+	public function get_serializer(): Serializer {
+		return new JsonSerializer();
+	}
+
+	public function get_template_name(): string {
 		return 'timepicker';
 	}
 }
diff --git a/src/Field/Traits/HtmlAttributes.php b/src/Field/Traits/HtmlAttributes.php
index 8116ee9f0218dc9568b9adffb28a331d07f48728..aac835dbaf3389d38cd4477a3e239840138285ce 100644
--- a/src/Field/Traits/HtmlAttributes.php
+++ b/src/Field/Traits/HtmlAttributes.php
@@ -2,6 +2,9 @@
 
 namespace WPDesk\Forms\Field\Traits;
 
+use WPDesk\Forms\Field;
+use WPDesk\Forms\Form;
+
 /**
  * Implementation of HTML attributes like id, name, action etc.
  *
@@ -9,61 +12,57 @@ namespace WPDesk\Forms\Field\Traits;
  */
 trait HtmlAttributes {
 
-	/** @var string[] */
-	protected $attributes;
+	/** @var array{placeholder: string, name: string, id: string, class: string[]} */
+	protected $attributes = [];
 
 	/**
 	 * Get list of all attributes except given.
 	 *
 	 * @param string[] $except
 	 *
-	 * @return string[]
+	 * @return array<string[]|string|bool>
 	 */
-	public function get_attributes( $except = [ 'name', 'type' ] ) {
-		return array_filter( $this->attributes, function ( $value, $key ) use ( $except ) {
-			return ! in_array( $key, $except, true );
-		}, ARRAY_FILTER_USE_BOTH );
+	final public function get_attributes( array $except = [ 'name', 'class' ] ): array {
+		return array_filter(
+			$this->attributes,
+			static function ( $key ) use ( $except ) {
+				return ! in_array( $key, $except, true );
+			},
+			ARRAY_FILTER_USE_KEY
+		);
 	}
 
 	/**
-	 * @param string $name
-	 * @param string $value
+	 * @param string               $name
+	 * @param string[]|string|bool $value
 	 *
-	 * @return $this
+	 * @return Field|Form
 	 */
-	public function set_attribute( $name, $value ) {
+	final public function set_attribute( string $name, $value ) {
 		$this->attributes[ $name ] = $value;
 
 		return $this;
 	}
 
 	/**
-	 * @param string $name
-	 *
-	 * @return $this
+	 * @return HtmlAttributes
 	 */
-	public function unset_attribute( $name ) {
+	final public function unset_attribute( string $name ) {
 		unset( $this->attributes[ $name ] );
 
 		return $this;
 	}
 
-	/**
-	 * @param string $name
-	 *
-	 * @return bool
-	 */
-	public function is_attribute_set( $name ) {
-		return isset( $this->attributes[ $name ] );
+	final public function is_attribute_set( string $name ): bool {
+		return ! empty( $this->attributes[ $name ] );
 	}
 
-	/**
-	 * @param string $name
-	 * @param mixed $default
-	 *
-	 * @return string
-	 */
-	public function get_attribute( $name, $default = null ) {
-		return $this->attributes[ $name ] ?? $default;
+	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 ?? '';
+		}
+
+		return (string) ( $this->attributes[ $name ] ?? $default ?? '' );
 	}
 }
diff --git a/src/Field/WooSelect.php b/src/Field/WooSelect.php
index b8d953808ca327e7ca4ef1ac0b305cd7410070bb..be348f932d60aa37e5540f9b8f7e01273837d49e 100644
--- a/src/Field/WooSelect.php
+++ b/src/Field/WooSelect.php
@@ -3,13 +3,13 @@
 namespace WPDesk\Forms\Field;
 
 class WooSelect extends SelectField {
+
 	public function __construct() {
-		parent::__construct();
 		$this->set_multiple();
 		$this->add_class( 'wc-enhanced-select' );
 	}
 
-	public function get_template_name() {
+	public function get_template_name(): string {
 		return 'woo-select';
 	}
 }
diff --git a/src/Field/WyswigField.php b/src/Field/WyswigField.php
index 846c91995b607526eec4c0b60e8c8cbd945186e6..145908fb3f8cf8be7af652ce1e9292e4235beeec 100644
--- a/src/Field/WyswigField.php
+++ b/src/Field/WyswigField.php
@@ -3,16 +3,12 @@
 namespace WPDesk\Forms\Field;
 
 class WyswigField extends BasicField {
-	public function __construct() {
-		parent::__construct();
-		$this->set_default_value( '' );
-	}
 
-	public function get_template_name() {
+	public function get_template_name(): string {
 		return 'wyswig';
 	}
 
-	public function should_override_form_template() {
+	public function should_override_form_template(): bool {
 		return true;
 	}
 }
diff --git a/src/FieldProvider.php b/src/FieldProvider.php
index f7f38419e4f0973c6e63f3e72fd67f40e91fb2e8..d8d281e43319fc784db71631ddbec2747115c334 100644
--- a/src/FieldProvider.php
+++ b/src/FieldProvider.php
@@ -6,10 +6,11 @@ namespace WPDesk\Forms;
  * FieldProvider is owner of FormFields. These fields can be used to render forms and process values.
  */
 interface FieldProvider {
+
 	/**
 	 * Returns owned fields.
 	 *
 	 * @return Field[]
 	 */
-	public function get_fields();
+	public function get_fields(): array;
 }
diff --git a/src/FieldRenderer.php b/src/FieldRenderer.php
index 37a356d6cfeb065577a6efd647769a954af7edf2..3e2191e20b80099d501c5e7811f4b3a6edebca34 100644
--- a/src/FieldRenderer.php
+++ b/src/FieldRenderer.php
@@ -2,16 +2,8 @@
 
 namespace WPDesk\Forms;
 
-/**
- * @class FieldRenderer
- */
 interface FieldRenderer {
-	/**
-	 * @param FieldProvider $provider
-	 * @param array $fields_data
-	 * @param string $name_prefix
-	 *
-	 * @return string|array String or normalized array
-	 */
-	public function render_fields( FieldProvider $provider, array $fields_data, $name_prefix = '' );
+
+	/** @return string|array String or normalized array */
+	public function render_fields( FieldProvider $provider, array $fields_data, string $name_prefix = '' );
 }
diff --git a/src/FieldsDataReceiver.php b/src/FieldsDataReceiver.php
index 9e2bdc9222ff58ff73be192494cbd9f60d96bb4b..961f8974d206a2e647bd55bd1a24d51dc125ef72 100644
--- a/src/FieldsDataReceiver.php
+++ b/src/FieldsDataReceiver.php
@@ -11,11 +11,10 @@ use Psr\Container\ContainerInterface;
  * @package WPDesk\Forms
  */
 interface FieldsDataReceiver {
+
 	/**
 	 * Set values corresponding to fields.
 	 *
-	 * @param ContainerInterface $data
-	 *
 	 * @return void
 	 */
 	public function update_fields_data( ContainerInterface $data );
diff --git a/src/Form.php b/src/Form.php
index 7450b2b1203750d62862f8af3b9980f3cf6b47e2..b5014ff6368e1b32d1e918d83cb9e914a5223ba2 100644
--- a/src/Form.php
+++ b/src/Form.php
@@ -10,57 +10,45 @@ use WPDesk\View\Renderer\Renderer;
  * @package WPDesk\Forms
  */
 interface Form {
+
 	/**
 	 * For some reason you may want to disable a form. Returns false when disabled.
-	 *
-	 * @return bool
 	 */
-	public function is_active();
+	public function is_active(): bool;
 
 	/**
 	 * Whether form handle_request method was successfully executed.
-	 *
-	 * @return bool
 	 */
-	public function is_submitted();
+	public function is_submitted(): bool;
 
 	/**
 	 * After handle_request or set_data the data in form can be invalid according to field validators.
-	 * Returns false when onle of them says the data is invalid.
-	 *
-	 * @return bool
+	 * Returns false when one of them says the data is invalid.
 	 */
-	public function is_valid();
+	public function is_valid(): bool;
 
 	/**
 	 * Add array to update data.
 	 *
 	 * @param array $request New data to update.
-	 */
-	public function handle_request( $request = array() );
-
-	/**
-	 * Data could be saved in some place. Use this method to transmit them to form.
 	 *
-	 * @param array $data Data for form.
+	 * @return void
 	 */
-	public function set_data( $data );
+	public function handle_request( array $request = [] );
 
 	/**
 	 * Use to render the form to string.
 	 *
 	 * @param Renderer $renderer Renderer to render form fields and form-templates.
-	 *
-	 * @return string
 	 */
-	public function render_form( Renderer $renderer );
+	public function render_form( Renderer $renderer ): string;
 
 	/**
 	 * Get data from form. Use after handle_request or set_data.
 	 *
-	 * @return array
+	 * @return array<int|string>
 	 */
-	public function get_data();
+	public function get_data(): array;
 
 	/**
 	 * Get data from form. Use after handle_request or set_data.
@@ -68,13 +56,11 @@ interface Form {
 	 *
 	 * @return array
 	 */
-	public function get_normalized_data();
+	public function get_normalized_data(): array;
 
 	/**
 	 * Form if you ever need to have more than one form at once.
-	 *
-	 * @return string
 	 */
-	public function get_form_id();
+	public function get_form_id(): string;
 }
 
diff --git a/src/Form/AbstractForm.php b/src/Form/AbstractForm.php
deleted file mode 100644
index b51a0e3661082a8018727991e80fd57ad7ba70ad..0000000000000000000000000000000000000000
--- a/src/Form/AbstractForm.php
+++ /dev/null
@@ -1,94 +0,0 @@
-<?php
-
-namespace WPDesk\Forms\Form;
-
-
-/**
- * Old abstraction layer for forms.
- *
- * @package WPDesk\Forms
- *
- * @deprecated Use ony for backward compatibility with Forms 1.x
- */
-abstract class AbstractForm {
-
-	/**
-	 * Unique form_id.
-	 *
-	 * @var string
-	 */
-	protected $form_id = 'form';
-
-	/**
-	 * Updated data.
-	 *
-	 * @var array
-	 */
-	protected $updated_data = array();
-
-
-	/**
-	 * Checks if form should be active.
-	 *
-	 * @return bool
-	 */
-	public function is_active() {
-		return true;
-	}
-
-	/**
-	 * Create form data and return an associative array.
-	 *
-	 * @return array
-	 */
-	abstract protected function create_form_data();
-
-	/**
-	 * Add array to update data.
-	 *
-	 * @param array $new_data new data to update.
-	 */
-	public function update_form_data( array $new_data = array() ) {
-		$this->updated_data = $new_data;
-	}
-
-	/**
-	 * Merge created and updated data and return associative array. Add to all keys form prefix.
-	 *
-	 * @return array
-	 */
-	public function get_form_data() {
-		return array_merge(
-			$this->create_form_data(),
-			$this->updated_data
-		);
-	}
-
-	/**
-	 * Get prefixed array returns array with prefixed form_id
-	 *
-	 * @return array
-	 */
-	public function get_prefixed_form_data() {
-		$array   = $this->get_form_data();
-		$form_id = $this->get_form_id();
-
-		return array_combine(
-			array_map( function ( $k ) use ( $form_id ) {
-				return $form_id . '_' . $k;
-			}, array_keys( $array ) ),
-			$array
-		);
-	}
-
-	/**
-	 * return form Id
-	 *
-	 * @return string
-	 */
-	public function get_form_id() {
-		return $this->form_id;
-	}
-
-}
-
diff --git a/src/Form/FormWithFields.php b/src/Form/FormWithFields.php
index 4b5819cd6393de3b5c70d6305e2ab17627818c00..8d4961d02c541ee2170cbf5b792509143461e7cb 100644
--- a/src/Form/FormWithFields.php
+++ b/src/Form/FormWithFields.php
@@ -7,7 +7,6 @@ use WPDesk\Forms\ContainerForm;
 use WPDesk\Forms\Field;
 use WPDesk\Forms\FieldProvider;
 use WPDesk\Forms\Form;
-use WPDesk\Persistence\Adapter\ArrayContainer;
 use WPDesk\Persistence\ElementNotExistsException;
 use WPDesk\Persistence\PersistentContainer;
 use WPDesk\View\Renderer\Renderer;
@@ -15,91 +14,61 @@ use WPDesk\View\Renderer\Renderer;
 class FormWithFields implements Form, ContainerForm, FieldProvider {
 	use Field\Traits\HtmlAttributes;
 
-	/**
-	 * Unique form_id.
-	 *
-	 * @var string
-	 */
+	/** @var string Unique form_id. */
 	protected $form_id = 'form';
-	/**
-	 * Updated data.
-	 *
-	 * @var array
-	 */
+
+	/** @var array Updated data. */
 	private $updated_data;
-	/**
-	 * Form fields.
-	 *
-	 * @var Field[]
-	 */
+
+	/** @var Field[] Form fields. */
 	private $fields;
 
 	/**
 	 * FormWithFields constructor.
 	 *
-	 * @param array $fields Form fields.
-	 * @param string $form_id Unique form id.
+	 * @param Field[] $fields
+	 * @param string  $form_id
 	 */
-	public function __construct( array $fields, $form_id = 'form' ) {
-		$this->fields       = $fields;
-		$this->form_id      = $form_id;
-		$this->updated_data = null;
+	public function __construct( array $fields, string $form_id = 'form' ) {
+		$this->fields  = $fields;
+		$this->form_id = $form_id;
+		$this->set_action( '' );
+		$this->set_method( 'POST' );
 	}
 
-	/**
-	 * Set Form action attribute.
-	 *
-	 * @param string $action
-	 */
-	public function set_action( $action ) {
+	/** Set Form action attribute. */
+	public function set_action( string $action ): self {
 		$this->attributes['action'] = $action;
 
 		return $this;
 	}
 
-	/**
-	 * Set Form method attribute ie. GET/POST.
-	 *
-	 * @param string $method
-	 */
-	public function set_method( $method ) {
+	public function get_action(): string {
+		return $this->attributes['action'];
+	}
+
+	/** Set Form method attribute ie. GET/POST. */
+	public function set_method( string $method ): self {
 		$this->attributes['method'] = $method;
 
 		return $this;
 	}
 
-	/**
-	 * @return string
-	 */
-	public function get_method() {
-		return isset( $this->attributes['method'] ) ? $this->attributes['method'] : 'POST';
+	public function get_method(): string {
+		return $this->attributes['method'];
 	}
 
-	/**
-	 * @return string
-	 */
-	public function get_action() {
-		return isset( $this->attributes['action'] ) ? $this->attributes['action'] : '';
-	}
 
-	/**
-	 * @inheritDoc
-	 */
-	public function is_submitted() {
+	public function is_submitted(): bool {
 		return null !== $this->updated_data;
 	}
 
-	/**
-	 * @inheritDoc
-	 */
+	/** @return void */
 	public function add_field( Field $field ) {
 		$this->fields[] = $field;
 	}
 
-	/**
-	 * @inheritDoc
-	 */
-	public function is_active() {
+	public function is_active(): bool {
 		return true;
 	}
 
@@ -107,17 +76,16 @@ class FormWithFields implements Form, ContainerForm, FieldProvider {
 	 * Add more fields to form.
 	 *
 	 * @param Field[] $fields Field to add to form.
+	 *
+	 * @return void
 	 */
 	public function add_fields( array $fields ) {
 		array_map( [ $this, 'add_field' ], $fields );
 	}
 
-	/**
-	 * @inheritDoc
-	 */
-	public function is_valid() {
+	public function is_valid(): bool {
 		foreach ( $this->fields as $field ) {
-			$field_value     = isset( $this->updated_data[ $field->get_name() ] ) ? $this->updated_data[ $field->get_name() ] : $field->get_default_value();
+			$field_value     = $this->updated_data[ $field->get_name() ] ?? $field->get_default_value();
 			$field_validator = $field->get_validator();
 			if ( ! $field_validator->is_valid( $field_value ) ) {
 				return false;
@@ -129,10 +97,8 @@ class FormWithFields implements Form, ContainerForm, FieldProvider {
 
 	/**
 	 * Add array to update data.
-	 *
-	 * @param array|ContainerInterface $request new data to update.
 	 */
-	public function handle_request( $request = array() ) {
+	public function handle_request( array $request = [] ) {
 		if ( $this->updated_data === null ) {
 			$this->updated_data = [];
 		}
@@ -147,12 +113,9 @@ class FormWithFields implements Form, ContainerForm, FieldProvider {
 	/**
 	 * Data could be saved in some place. Use this method to transmit them to form.
 	 *
-	 * @param array|ContainerInterface $data Data consistent with Form and ContainerForm interface.
+	 * @return void
 	 */
-	public function set_data( $data ) {
-		if ( is_array( $data ) ) {
-			$data = new ArrayContainer( $data );
-		}
+	public function set_data( ContainerInterface $data ) {
 		foreach ( $this->fields as $field ) {
 			$data_key = $field->get_name();
 			if ( $data->has( $data_key ) ) {
@@ -165,48 +128,41 @@ class FormWithFields implements Form, ContainerForm, FieldProvider {
 		}
 	}
 
-	/**
-	 * Renders only fields without form.
-	 *
-	 * @param Renderer $renderer
-	 *
-	 * @return string
-	 */
-	public function render_fields( Renderer $renderer ) {
+	/** Renders only fields without form. */
+	public function render_fields( Renderer $renderer ): string {
 		$content     = '';
 		$fields_data = $this->get_data();
 		foreach ( $this->get_fields() as $field ) {
-			$content .= $renderer->render( $field->should_override_form_template() ? $field->get_template_name() : 'form-field',
+			$content .= $renderer->render(
+				$field->should_override_form_template() ? $field->get_template_name() : 'form-field',
 				[
 					'field'         => $field,
 					'renderer'      => $renderer,
 					'name_prefix'   => $this->get_form_id(),
-					'value'         => isset( $fields_data[ $field->get_name() ] ) ? $fields_data[ $field->get_name() ] : $field->get_default_value(),
+					'value'         => $fields_data[ $field->get_name() ] ?? $field->get_default_value(),
 					'template_name' => $field->get_template_name(),
-				] );
+				]
+			);
 		}
 
 		return $content;
 	}
 
-	/**
-	 * @inheritDoc
-	 */
-	public function render_form( Renderer $renderer ) {
-		$content = $renderer->render( 'form-start', [
-			'form'   => $this,
-			'method' => $this->get_method(), // backward compat
-			'action' => $this->get_action(),  // backward compat
-		] );
+	public function render_form( Renderer $renderer ): string {
+		$content = $renderer->render(
+			'form-start',
+			[
+				'form'   => $this,
+				'method' => $this->get_method(), // backward compat.
+				'action' => $this->get_action(),  // backward compat.
+			]
+		);
 		$content .= $this->render_fields( $renderer );
 		$content .= $renderer->render( 'form-end' );
 
 		return $content;
 	}
 
-	/**
-	 * @inheritDoc
-	 */
 	public function put_data( PersistentContainer $container ) {
 		foreach ( $this->get_fields() as $field ) {
 			$data_key = $field->get_name();
@@ -223,10 +179,11 @@ class FormWithFields implements Form, ContainerForm, FieldProvider {
 		}
 	}
 
-	/**
-	 * @inheritDoc
-	 */
-	public function get_data() {
+	public function get_data(): array {
+		if ( empty( $this->get_fields() ) ) {
+			return [];
+		}
+
 		$data = $this->updated_data;
 
 		foreach ( $this->get_fields() as $field ) {
@@ -239,24 +196,24 @@ class FormWithFields implements Form, ContainerForm, FieldProvider {
 		return $data;
 	}
 
-	/**
-	 * @inheritDoc
-	 */
-	public function get_fields() {
-		return $this->fields;
+	public function get_fields(): array {
+		$fields = $this->fields;
+
+		usort(
+			$fields,
+			static function ( Field $a, Field $b ) {
+				return $a->get_priority() <=> $b->get_priority();
+			}
+		);
+
+		return $fields;
 	}
 
-	/**
-	 * @inheritDoc
-	 */
-	public function get_form_id() {
+	public function get_form_id(): string {
 		return $this->form_id;
 	}
 
-	/**
-	 * @inheritDoc
-	 */
-	public function get_normalized_data() {
+	public function get_normalized_data(): array {
 		return $this->get_data();
 	}
 }
diff --git a/src/Form/FormsCollection.php b/src/Form/FormsCollection.php
deleted file mode 100644
index f2603a31e043719383c1f59a3385aa97b77ee272..0000000000000000000000000000000000000000
--- a/src/Form/FormsCollection.php
+++ /dev/null
@@ -1,115 +0,0 @@
-<?php
-
-namespace WPDesk\Forms\Form;
-
-use WPDesk\Forms\Form;
-
-/**
- * FormsCollection class store AbstractForm instances and merges forms data from all collections
- *
- * @deprecated Use ony for backward compatibility with Forms 1.x
- *
- * @package WPDesk\Forms
- */
-class FormsCollection {
-	/**
-	 * AbstractForm array collection.
-	 *
-	 * @var Form[]
-	 */
-	protected $forms = array();
-
-	/**
-	 * Unique form_id.
-	 *
-	 * @var string
-	 */
-	protected $form_id = 'form';
-
-	/**
-	 * Updated data.
-	 *
-	 * @var array
-	 */
-	protected $updated_data = array();
-
-	/**
-	 * Add forms. All keys in this array must be unique, otherwise add_form will throw exception.
-	 *
-	 * @param Form[] $forms
-	 */
-	public function add_forms( array $forms = array() ) {
-		foreach ( $forms as $form_object ) {
-			$this->add_form( $form_object );
-		}
-	}
-
-	/**
-	 * Add form. If key is not unique throw exception.
-	 *
-	 * @param Form $form
-	 *
-	 * @throws \OutOfBoundsException
-	 */
-	public function add_form( AbstractForm $form ) {
-		if ( ! $this->is_form_exists( $form->get_form_id() ) ) {
-			$this->forms[ $form->get_form_id() ] = $form;
-		} else {
-			throw new \OutOfBoundsException( 'Form with this key already exists' );
-		}
-	}
-
-	/**
-	 * Is form exists. Checks if key exists in the array of forms and return bool.
-	 *
-	 * @param string $form_id
-	 *
-	 * @return bool
-	 */
-	public function is_form_exists( $form_id ) {
-		return isset( $this->forms[ (string) $form_id ] );
-	}
-
-	/**
-	 * Get form.
-	 *
-	 * @param string $form_id
-	 *
-	 * @return Form
-	 * @throws \OutOfRangeException
-	 */
-	public function get_form( $form_id ) {
-		if ( $this->is_form_exists( $form_id ) ) {
-			return $this->forms[ (string) $form_id ];
-		}
-
-		throw new \OutOfRangeException( 'Form with this key not exists' );
-	}
-
-	/**
-	 * Get forms data. This method merge all arrays from forms and return associative array for woocommerce form_fields.
-	 *
-	 * @param bool $prefixed if true add form_id as prefix to form keys
-	 *
-	 * @return array
-	 */
-	public function get_forms_data( $prefixed = false ) {
-
-		$forms_data = array();
-
-		foreach ( $this->forms as $form ) {
-			if ( ! $form->is_active() ) {
-				continue;
-			}
-
-			if ( $prefixed ) {
-				$forms_data = array_merge( $forms_data, $form->get_prefixed_form_data() );
-			} else {
-				$forms_data = array_merge( $forms_data, $form->get_form_data() );
-			}
-		}
-
-		return $forms_data;
-	}
-
-}
diff --git a/src/Persistence/FieldPersistenceStrategy.php b/src/Persistence/FieldPersistenceStrategy.php
index 092a01a61643fc5c0ce762a64a38c264f6637b90..3102f1ce6f69d8f9dc6f790a9a885e92076d3e39 100644
--- a/src/Persistence/FieldPersistenceStrategy.php
+++ b/src/Persistence/FieldPersistenceStrategy.php
@@ -12,6 +12,7 @@ use WPDesk\Persistence\PersistentContainer;
  * @package WPDesk\Forms
  */
 class FieldPersistenceStrategy {
+
 	/** @var PersistentContainer */
 	private $persistence;
 
@@ -19,33 +20,32 @@ class FieldPersistenceStrategy {
 		$this->persistence = $persistence;
 	}
 
-	/**
-	 * Save fields data.
-	 *
-	 * @param FieldProvider $fields_provider
-	 * @param array $data
-	 */
+	/** @return void */
 	public function persist_fields( FieldProvider $fields_provider, array $data ) {
 		foreach ( $fields_provider->get_fields() as $field ) {
 			$field_key = $field->get_name();
-			$this->persistence->set( $field_key, $field->get_serializer()->serialize( $data[ $field_key ] ) );
+			if ( $field->has_serializer() ) {
+				$this->persistence->set( $field_key, $field->get_serializer()->serialize( $data[ $field_key ] ) );
+			} else {
+				$this->persistence->set( $field_key, $data[ $field_key ] );
+			}
 		}
 	}
 
-	/**
-	 * Load fields data.
-	 *
-	 * @return array
-	 */
-	public function load_fields( FieldProvider $fields_provider ) {
+	/** @return void */
+	public function load_fields( FieldProvider $fields_provider ): array {
 		$data = [];
 		foreach ( $fields_provider->get_fields() as $field ) {
 			$field_key = $field->get_name();
 			try {
-				$data[ $field_key ] = $field->get_serializer()->unserialize( $this->persistence->get( $field_key ) );
+				if ( $field->has_serializer() ) {
+					$data[ $field_key ] = $field->get_serializer()->unserialize( $this->persistence->get( $field_key ) );
+				} 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" );
+				// LoggerFactory::get_logger()->info( "FieldPersistenceStrategy:: Field {$field_key} not found" );
 			}
 		}
 
diff --git a/src/Renderer/JsonNormalizedRenderer.php b/src/Renderer/JsonNormalizedRenderer.php
index 4d078ac531786a23d18b6e24293c13149540e0b9..da9e1f8cb103a51b548943033c40b270192ef103 100644
--- a/src/Renderer/JsonNormalizedRenderer.php
+++ b/src/Renderer/JsonNormalizedRenderer.php
@@ -2,6 +2,7 @@
 
 namespace WPDesk\Forms\Renderer;
 
+use WPDesk\Forms\Field;
 use WPDesk\Forms\FieldProvider;
 use WPDesk\Forms\FieldRenderer;
 
@@ -13,28 +14,30 @@ use WPDesk\Forms\FieldRenderer;
 class JsonNormalizedRenderer implements FieldRenderer {
 	/**
 	 * @param FieldProvider $provider
-	 * @param array $fields_data
-	 * @param string $name_prefix
+	 * @param array         $fields_data
+	 * @param string        $name_prefix
 	 *
 	 * @return array Normalized fields with data.
 	 */
-	public function render_fields( FieldProvider $provider, array $fields_data, $name_prefix = '' ) {
+	public function render_fields( FieldProvider $provider, array $fields_data, string $name_prefix = '' ): array {
 		$rendered_fields = [];
-		foreach ( $provider->get_fields() as $field ) {
-			$rendered = [
-				'name'     => $field->get_name(),
-				'template' => $field->get_template_name(),
-				'multiple' => $field->is_multiple(),
-				'disabled' => $field->is_disabled(),
-				'readonly' => $field->is_readonly(),
-				'required' => $field->is_required(),
-				'prefix'   => $name_prefix,
-				'value '   => isset( $fields_data[ $field->get_name() ] ) ? $fields_data[ $field->get_name() ] : $field->get_default_value()
-			];
-
-			if ( $field->has_classes() ) {
-				$rendered['class'] = $field->get_classes();
+		$fields          = $provider->get_fields();
+		usort(
+			$fields,
+			static function ( Field $a, Field $b ) {
+				return $a->get_priority() <=> $b->get_priority();
+			}
+		);
+		foreach ( $fields as $field ) {
+			$rendered = [];
+			foreach ( $field->get_attributes() as $key => $attribute ) {
+				$rendered[ $key ] = $attribute;
 			}
+			$rendered['name']     = $field->get_name();
+			$rendered['template'] = $field->get_template_name();
+			$rendered['prefix']   = $name_prefix;
+			$rendered['value']    = $fields_data[ $field->get_name() ] ?? $field->get_default_value();
+
 			if ( $field->has_description() ) {
 				$rendered['description'] = $field->get_description();
 			}
@@ -44,15 +47,12 @@ class JsonNormalizedRenderer implements FieldRenderer {
 			if ( $field->has_label() ) {
 				$rendered['label'] = $field->get_label();
 			}
-			if ( $field->has_placeholder() ) {
-				$rendered['placeholder'] = $field->get_placeholder();
-			}
 			$options = $field->get_possible_values();
 			if ( $options ) {
 				$rendered['options'] = $options;
 			}
 			if ( $field->has_data() ) {
-				$data = $field->get_data();
+				$data             = $field->get_data();
 				$rendered['data'] = [];
 				foreach ( $data as $data_name => $data_value ) {
 					$rendered['data'][] = [
@@ -61,7 +61,7 @@ class JsonNormalizedRenderer implements FieldRenderer {
 					];
 				}
 			}
-			if (json_encode($rendered) !== false) {
+			if ( json_encode( $rendered ) !== false ) {
 				$rendered_fields[] = $rendered;
 			}
 		}
diff --git a/src/Resolver/DefaultFormFieldResolver.php b/src/Resolver/DefaultFormFieldResolver.php
index 3609a6973a3137ce3e99a227c42f85c6650a9212..b5c167502452dc45e910619ab753b69d5b710a80 100644
--- a/src/Resolver/DefaultFormFieldResolver.php
+++ b/src/Resolver/DefaultFormFieldResolver.php
@@ -12,6 +12,7 @@ use WPDesk\View\Resolver\Resolver;
  * @package WPDesk\Forms\Resolver
  */
 class DefaultFormFieldResolver implements Resolver {
+
 	/** @var Resolver */
 	private $dir_resolver;
 
@@ -19,8 +20,8 @@ class DefaultFormFieldResolver implements Resolver {
 		$this->dir_resolver = new DirResolver( __DIR__ . '/../../templates' );
 	}
 
-	public function resolve( $name, Renderer $renderer = null ) {
+	public function resolve( $name, Renderer $renderer = null ): string {
 		return $this->dir_resolver->resolve( $name, $renderer );
 	}
 
-}
\ No newline at end of file
+}
diff --git a/src/Sanitizer.php b/src/Sanitizer.php
index a7883dcf74f7927763d47285764dea3471be9dad..000d3aa9419a2c2e76bebb69bf46fc50d17539c8 100644
--- a/src/Sanitizer.php
+++ b/src/Sanitizer.php
@@ -3,10 +3,11 @@
 namespace WPDesk\Forms;
 
 interface Sanitizer {
+
 	/**
 	 * @param mixed $value
 	 *
-	 * @return string
+	 * @return mixed
 	 */
 	public function sanitize( $value );
 }
diff --git a/src/Sanitizer/CallableSanitizer.php b/src/Sanitizer/CallableSanitizer.php
index c0a28b9756d8d18a12be39d645e643d6d828fcf9..da916abbb4971b884fb1f37368d8174299f35593 100644
--- a/src/Sanitizer/CallableSanitizer.php
+++ b/src/Sanitizer/CallableSanitizer.php
@@ -5,13 +5,15 @@ namespace WPDesk\Forms\Sanitizer;
 use WPDesk\Forms\Sanitizer;
 
 class CallableSanitizer implements Sanitizer {
+
+	/** @var callable */
 	private $callable;
 
-	public function __construct( $callable ) {
+	public function __construct( callable $callable ) {
 		$this->callable = $callable;
 	}
 
-	public function sanitize( $value ) {
+	public function sanitize( $value ): string {
 		return call_user_func( $this->callable, $value );
 	}
 
diff --git a/src/Sanitizer/EmailSanitizer.php b/src/Sanitizer/EmailSanitizer.php
new file mode 100644
index 0000000000000000000000000000000000000000..160ef3c7d20e6f1fdc38fbee9a7be97edd8ca896
--- /dev/null
+++ b/src/Sanitizer/EmailSanitizer.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace WPDesk\Forms\Sanitizer;
+
+use WPDesk\Forms\Sanitizer;
+
+class EmailSanitizer implements Sanitizer {
+
+	public function sanitize( $value ): string {
+		return sanitize_email( $value );
+	}
+
+}
diff --git a/src/Sanitizer/NoSanitize.php b/src/Sanitizer/NoSanitize.php
index a3fe4a61160f130cae2821f2bf9faa8352d761c7..2647e663aed006e51bd7c0bf89a5b981d03618dd 100644
--- a/src/Sanitizer/NoSanitize.php
+++ b/src/Sanitizer/NoSanitize.php
@@ -5,6 +5,7 @@ namespace WPDesk\Forms\Sanitizer;
 use WPDesk\Forms\Sanitizer;
 
 class NoSanitize implements Sanitizer {
+
 	public function sanitize( $value ) {
 		return $value;
 	}
diff --git a/src/Sanitizer/TextFieldSanitizer.php b/src/Sanitizer/TextFieldSanitizer.php
index 0a1bbcbca0f7f12580bfa0fb09ca78c3da9f82e4..2fa5e3f7db91859f882d64e927b67034ffbdc6f8 100644
--- a/src/Sanitizer/TextFieldSanitizer.php
+++ b/src/Sanitizer/TextFieldSanitizer.php
@@ -5,7 +5,12 @@ namespace WPDesk\Forms\Sanitizer;
 use WPDesk\Forms\Sanitizer;
 
 class TextFieldSanitizer implements Sanitizer {
+
+	/** @return string|string[] */
 	public function sanitize( $value ) {
+		if ( is_array( $value ) ) {
+			return array_map('sanitize_text_field', $value);
+		}
 		return sanitize_text_field( $value );
 	}
 
diff --git a/src/Serializer.php b/src/Serializer.php
index 91054006581db530155966b9482962d72384761e..ad5f888bd91d800cd3f964ff966ea9d47b378f21 100644
--- a/src/Serializer.php
+++ b/src/Serializer.php
@@ -3,7 +3,14 @@
 namespace WPDesk\Forms;
 
 interface Serializer {
-	public function serialize( $value );
 
-	public function unserialize( $value );
+	/**
+	 * @param mixed $value
+	 */
+	public function serialize( $value ): string;
+
+	/**
+	 * @return mixed
+	 */
+	public function unserialize( string $value );
 }
diff --git a/src/Serializer/JsonSerializer.php b/src/Serializer/JsonSerializer.php
index d7e5ca038aedd247e7024785efed0cad27d0dc7d..0048820b331ff11ec4200a8148cd71d8a1a27a29 100644
--- a/src/Serializer/JsonSerializer.php
+++ b/src/Serializer/JsonSerializer.php
@@ -5,11 +5,12 @@ namespace WPDesk\Forms\Serializer;
 use WPDesk\Forms\Serializer;
 
 class JsonSerializer implements Serializer {
-	public function serialize( $value ) {
-		return json_encode( $value );
+
+	public function serialize( $value ): string {
+		return (string) json_encode( $value );
 	}
 
-	public function unserialize( $value ) {
+	public function unserialize( string $value ) {
 		return json_decode( $value, true );
 	}
 }
diff --git a/src/Serializer/NoSerialize.php b/src/Serializer/NoSerialize.php
deleted file mode 100644
index b29e88b9e23420a2c67b5bd2f10136d8f509edfa..0000000000000000000000000000000000000000
--- a/src/Serializer/NoSerialize.php
+++ /dev/null
@@ -1,16 +0,0 @@
-<?php
-
-namespace WPDesk\Forms\Serializer;
-
-use WPDesk\Forms\Serializer;
-
-class NoSerialize implements Serializer {
-	public function serialize( $value ) {
-		return $value;
-	}
-
-	public function unserialize( $value ) {
-		return $value;
-	}
-
-}
diff --git a/src/Serializer/ProductSelectSerializer.php b/src/Serializer/ProductSelectSerializer.php
index 2adf327996bc3d2ad6f383a72a26642e9161cb92..dd034f0786c4085f2621148e1249259ba1bfd38b 100644
--- a/src/Serializer/ProductSelectSerializer.php
+++ b/src/Serializer/ProductSelectSerializer.php
@@ -10,10 +10,8 @@ use WPDesk\Forms\Serializer;
  * @package WPDesk\Forms\Serializer
  */
 class ProductSelectSerializer implements Serializer {
-	/**
-	 * @param $value
-	 */
-	public function serialize( $value ) {
+
+	public function serialize( $value ): string {
 		$products_with_names = [];
 		if ( is_array( $value ) ) {
 			foreach ( $value as $product_id ) {
@@ -25,10 +23,10 @@ class ProductSelectSerializer implements Serializer {
 			}
 		}
 
-		return $products_with_names;
+		return implode( ' ', $products_with_names );
 	}
 
-	public function unserialize( $value ) {
+	public function unserialize( string $value ) {
 		return $value;
 	}
 
diff --git a/src/Serializer/SerializeSerializer.php b/src/Serializer/SerializeSerializer.php
index 79d13f6fecfc1d300a4f5a174190b6f4d0e49c0f..776bd76f22628335d58466c3dc257761ca28b8b4 100644
--- a/src/Serializer/SerializeSerializer.php
+++ b/src/Serializer/SerializeSerializer.php
@@ -5,11 +5,12 @@ namespace WPDesk\Forms\Serializer;
 use WPDesk\Forms\Serializer;
 
 class SerializeSerializer implements Serializer {
-	public function serialize( $value ) {
+
+	public function serialize( $value ): string {
 		return serialize( $value );
 	}
 
-	public function unserialize( $value ) {
+	public function unserialize( string $value ) {
 		return unserialize( $value );
 	}
 
diff --git a/src/Validator.php b/src/Validator.php
index d953702d23861c2af778c70b4027d8cfb211f793..9422fdb4bd63013fa5553fc702545dd3380b9ac7 100644
--- a/src/Validator.php
+++ b/src/Validator.php
@@ -3,15 +3,10 @@
 namespace WPDesk\Forms;
 
 interface Validator {
-	/**
-	 * @param mixed $value
-	 *
-	 * @return bool
-	 */
-	public function is_valid( $value );
 
-	/**
-	 * @return string[]
-	 */
-	public function get_messages();
+	/** @param mixed $value */
+	public function is_valid( $value ): bool;
+
+	/** @return string[] */
+	public function get_messages(): array;
 }
diff --git a/src/Validator/ChainValidator.php b/src/Validator/ChainValidator.php
index d3549d566ed861b8ccd7caef78734a1e2dc321cb..341abeec541d8fa18757c808aa69e1635a629d05 100644
--- a/src/Validator/ChainValidator.php
+++ b/src/Validator/ChainValidator.php
@@ -5,9 +5,11 @@ namespace WPDesk\Forms\Validator;
 use WPDesk\Forms\Validator;
 
 class ChainValidator implements Validator {
+
 	/** @var Validator[] */
 	private $validators;
 
+	/** @var array */
 	private $messages;
 
 	public function __construct() {
@@ -20,13 +22,13 @@ class ChainValidator implements Validator {
 	 *
 	 * @return $this
 	 */
-	public function attach( Validator $validator ) {
+	public function attach( Validator $validator ): self {
 		$this->validators[] = $validator;
 
 		return $this;
 	}
 
-	public function is_valid( $value ) {
+	public function is_valid( $value ): bool {
 		$result   = true;
 		$messages = [ [] ];
 		foreach ( $this->validators as $validator ) {
@@ -40,7 +42,7 @@ class ChainValidator implements Validator {
 		return $result;
 	}
 
-	public function get_messages() {
+	public function get_messages(): array {
 		return $this->messages;
 	}
 
diff --git a/src/Validator/NoValidateValidator.php b/src/Validator/NoValidateValidator.php
index dbc996646da8d0fb7d0128404abcb90669d61202..d3bf32f4dfb919ab53b5f2c5c53657f6ab9a4e71 100644
--- a/src/Validator/NoValidateValidator.php
+++ b/src/Validator/NoValidateValidator.php
@@ -5,11 +5,12 @@ namespace WPDesk\Forms\Validator;
 use WPDesk\Forms\Validator;
 
 class NoValidateValidator implements Validator {
-	public function is_valid( $value ) {
+
+	public function is_valid( $value ): bool {
 		return true;
 	}
 
-	public function get_messages() {
+	public function get_messages(): array {
 		return [];
 	}
 
diff --git a/src/Validator/NonceValidator.php b/src/Validator/NonceValidator.php
index 87682257b08d4c14525a80a4f30f3082d0ba6014..b8874289fde8342e868b9df682558bfe75ea3025 100644
--- a/src/Validator/NonceValidator.php
+++ b/src/Validator/NonceValidator.php
@@ -5,19 +5,20 @@ namespace WPDesk\Forms\Validator;
 use WPDesk\Forms\Validator;
 
 class NonceValidator implements Validator {
+
+	/** @var string */
 	private $action;
 
+	/** @param string $action */
 	public function __construct( $action ) {
 		$this->action = $action;
 	}
 
-	public function is_valid( $value ) {
-		$valid = wp_verify_nonce( $value, $this->action );
-
-		return $valid;
+	public function is_valid( $value ): bool {
+		return (bool) wp_verify_nonce( $value, $this->action );
 	}
 
-	public function get_messages() {
+	public function get_messages(): array {
 		return [];
 	}
 
diff --git a/src/Validator/RequiredValidator.php b/src/Validator/RequiredValidator.php
index 016d803d86a95cf3c4c4dd3b6bf9a189c1edffa3..5d4851abba5c34a9e000727c672dc94195dba496 100644
--- a/src/Validator/RequiredValidator.php
+++ b/src/Validator/RequiredValidator.php
@@ -5,11 +5,12 @@ namespace WPDesk\Forms\Validator;
 use WPDesk\Forms\Validator;
 
 class RequiredValidator implements Validator {
-	public function is_valid( $value ) {
+
+	public function is_valid( $value ): bool {
 		return $value !== null;
 	}
 
-	public function get_messages() {
+	public function get_messages(): array {
 		return [];
 	}
 
diff --git a/templates/button.php b/templates/button.php
index 214417d3968ccc53ed7713ec66e35f2d5ee3b2d3..3068ae66513ef2017dfc3f7164f331c8e5b691eb 100644
--- a/templates/button.php
+++ b/templates/button.php
@@ -10,27 +10,16 @@
 ?>
 
 <button
-<?php if ( $field->has_classes() ) : ?>
-	class="<?php echo \esc_attr( $field->get_classes() ); ?>"
-<?php endif; ?>
-
-<?php foreach ( $field->get_attributes( [] ) as $key => $val ) : ?>
-	<?php echo \esc_attr( $key ) . '="' . \esc_attr( $val ) . '"'; ?>
-<?php endforeach; ?>
-
 	type="<?php echo \esc_attr( $field->get_type() ); ?>"
 	name="<?php echo \esc_attr( $name_prefix ) . '[' . \esc_attr( $field->get_name() ) . ']'; ?>"
 	id="<?php echo \esc_attr( $field->get_id() ); ?>"
 	value="<?php echo \esc_html( $value ); ?>"
-
-	<?php
-	if ( $field->is_disabled() ) :
-		?>
-		disabled="disabled"<?php endif; ?>
-	<?php
-	if ( $field->is_readonly() ) :
-		?>
-		readonly="readonly"<?php endif; ?>
+	<?php if ( $field->has_classes() ) : ?>
+		class="<?php echo \esc_attr( $field->get_classes() ); ?>"
+	<?php endif; ?>
+	<?php foreach ( $field->get_attributes() as $key => $val ) : ?>
+		<?php echo \esc_attr( $key ) . '="' . \esc_attr( $val ) . '"'; ?>
+	<?php endforeach; ?>
 
 ><?php echo \esc_html( $field->get_label() ); ?></button>
 
diff --git a/templates/input-submit.php b/templates/input-submit.php
index bae3ce3c40857e82c287fc75e70827a468e288af..176d6d72abd5792ab97f266e3b3900531614bf16 100644
--- a/templates/input-submit.php
+++ b/templates/input-submit.php
@@ -13,29 +13,15 @@
 	<td style="padding-left:0;">
 		<p class="submit">
 			<input
-				<?php
-				if ( $field->has_classes() ) :
-					?>
-					class="<?php echo \esc_attr( $field->get_classes() ); ?>"<?php endif; ?>
-				<?php foreach ( $field->get_attributes( [] ) as $key => $value ) : ?>
+				<?php if ( $field->has_classes() ) : ?>
+					class="<?php echo \esc_attr( $field->get_classes() ); ?>"
+				<?php endif; ?>
+				<?php foreach ( $field->get_attributes() as $key => $value ) : ?>
 					<?php echo \esc_attr( $key ); ?>="<?php echo \esc_attr( $value ); ?>"
 				<?php endforeach; ?>
 				type="<?php echo \esc_attr( $field->get_type() ); ?>"
 				name="<?php echo \esc_attr( $name_prefix ); ?>[<?php echo \esc_attr( $field->get_name() ); ?>]"
-				id="<?php echo \esc_attr( $field->get_id() ); ?>"
 				value="<?php echo \esc_html( $field->get_label() ); ?>"
-				<?php
-				if ( $field->is_required() ) :
-					?>
-					required="required"<?php endif; ?>
-				<?php
-				if ( $field->is_disabled() ) :
-					?>
-					disabled="disabled"<?php endif; ?>
-				<?php
-				if ( $field->is_readonly() ) :
-					?>
-					readonly="readonly"<?php endif; ?>
 			/>
 		</p>
 	</td>
diff --git a/templates/input-text-multiple.php b/templates/input-text-multiple.php
index 371013d460c08017247573947a6c2f54e7a9a86a..05e7d29abd4d8744cdffef854dd7ecff536825f1 100644
--- a/templates/input-text-multiple.php
+++ b/templates/input-text-multiple.php
@@ -43,18 +43,6 @@ if ( empty( $value ) || is_string( $value ) ) {
 			?>
 		<?php endforeach; ?>
 
-		<?php
-		if ( $field->is_required() ) :
-			?>
-			required="required"<?php endif; ?>
-		<?php
-		if ( $field->is_disabled() ) :
-			?>
-			disabled="disabled"<?php endif; ?>
-		<?php
-		if ( $field->is_readonly() ) :
-			?>
-			readonly="readonly"<?php endif; ?>
 		<?php if ( \in_array( $field->get_type(), [ 'number', 'text', 'hidden' ], true ) ) : ?>
 			value="<?php echo \esc_html( $text_value ); ?>"
 		<?php else : ?>
diff --git a/templates/input.php b/templates/input.php
index a4844b1c1e8ec1be5ea9b32532c5c0873f0f91ca..4b8eb6a5bc3c3e485e51a07ed8e1107a8d053ac2 100644
--- a/templates/input.php
+++ b/templates/input.php
@@ -17,34 +17,17 @@ if ( $field->get_type() === 'checkbox' && $field->has_sublabel() ) :
 <input
 	type="<?php echo \esc_attr( $field->get_type() ); ?>"
 	name="<?php echo \esc_attr( $name_prefix ) . '[' . \esc_attr( $field->get_name() ) . ']'; ?>"
-	id="<?php echo \esc_attr( $field->get_id() ); ?>"
 
 	<?php if ( $field->has_classes() ) : ?>
 		class="<?php echo \esc_attr( $field->get_classes() ); ?>"
 	<?php endif; ?>
 
-	<?php if ( $field->get_type() === 'text' && $field->has_placeholder() ) : ?>
-		placeholder="<?php echo \esc_html( $field->get_placeholder() ); ?>"
-	<?php endif; ?>
-
 	<?php
 	foreach ( $field->get_attributes() as $key => $atr_val ) :
 		echo \esc_attr( $key ) . '="' . \esc_attr( $atr_val ) . '"';
 		?>
 	<?php endforeach; ?>
 
-	<?php
-	if ( $field->is_required() ) :
-		?>
-		required="required"<?php endif; ?>
-	<?php
-	if ( $field->is_disabled() ) :
-		?>
-		disabled="disabled"<?php endif; ?>
-	<?php
-	if ( $field->is_readonly() ) :
-		?>
-		readonly="readonly"<?php endif; ?>
 	<?php if ( \in_array( $field->get_type(), [ 'number', 'text', 'hidden' ], true ) ) : ?>
 		value="<?php echo \esc_html( $value ); ?>"
 	<?php else : ?>
diff --git a/templates/select.php b/templates/select.php
index 625c316bef04162c50b397f3759d4a623190b36f..edb7e0621ba435257982e3fec164cfc0974c4b35 100644
--- a/templates/select.php
+++ b/templates/select.php
@@ -17,23 +17,6 @@
 	<?php foreach ( $field->get_attributes() as $key => $attr_val ) : ?>
 		<?php echo \esc_attr( $key ); ?>="<?php echo \esc_attr( $attr_val ); ?>"
 	<?php endforeach; ?>
-
-	<?php
-	if ( $field->is_required() ) :
-		?>
-		required="required"<?php endif; ?>
-	<?php
-	if ( $field->is_disabled() ) :
-		?>
-		disabled="disabled"<?php endif; ?>
-	<?php
-	if ( $field->is_readonly() ) :
-		?>
-		readonly="readonly"<?php endif; ?>
-	<?php
-	if ( $field->is_multiple() ) :
-		?>
-		multiple="multiple"<?php endif; ?>
 >
 	<?php
 	if ( $field->has_placeholder() ) :
diff --git a/templates/textarea.php b/templates/textarea.php
index 8cf3846d8c3fd9e15175f04951845d4a6f071687..218f8607e1a7d6e12d53964b7fdfeac52eec1314 100644
--- a/templates/textarea.php
+++ b/templates/textarea.php
@@ -18,23 +18,6 @@
 		<?php echo \esc_attr( $key ); ?>="<?php echo \esc_attr( $attr_val ); ?>"
 	<?php endforeach; ?>
 
-	<?php
-	if ( $field->is_required() ) :
-		?>
-		required="required"<?php endif; ?>
-	<?php
-	if ( $field->is_disabled() ) :
-		?>
-		disabled="disabled"<?php endif; ?>
-	<?php
-	if ( $field->is_readonly() ) :
-		?>
-		readonly="readonly"<?php endif; ?>
-	<?php
-	if ( $field->is_multiple() ) :
-		?>
-		multiple="multiple"<?php endif; ?>
-
 	<?php
 	if ( $field->has_placeholder() ) :
 		?>
diff --git a/tests/docker-compose.yaml b/tests/docker-compose.yaml
index 2a86b036f682ed318b27b028d2e96dfcd5979afd..718ce5ee9b705df6bf890545090cbeecbbcacb9c 100644
--- a/tests/docker-compose.yaml
+++ b/tests/docker-compose.yaml
@@ -123,7 +123,7 @@ services:
       WORDPRESS_DB_HOST: mysql0
 
   mysql0:
-    image: mysql:5.7
+    image: mariadb:10.3
     environment:
       MYSQL_ROOT_PASSWORD: mysql
       MYSQL_DATABASE: wptest
diff --git a/tests/integration/Form/FormWithFieldsTest.php b/tests/integration/Form/FormWithFieldsTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..165592dbc5b08efa1fad4a69dec9bae17cc3d5a8
--- /dev/null
+++ b/tests/integration/Form/FormWithFieldsTest.php
@@ -0,0 +1,77 @@
+<?php
+
+namespace integration\Form;
+
+use WPDesk\Forms\Field\InputNumberField;
+use WPDesk\Forms\Field\InputTextField;
+use WPDesk\Forms\Field\SelectField;
+use WPDesk\Forms\Form\FormWithFields;
+use PHPUnit\Framework\TestCase;
+
+class FormWithFieldsTest extends TestCase {
+
+	/** @var FormWithFields */
+	private $form;
+
+	protected function setUp() {
+		$this->form = new FormWithFields([]);
+	}
+
+	public function test_should_return_fields_sorted_by_priority() {
+		$this->form->add_fields(
+			[
+				( new InputTextField() )
+				->set_label('fourth'),
+				( new SelectField() )
+				->set_label('second')
+				->set_priority(5),
+				( new SelectField() )
+				->set_label('third')
+				->set_priority(7),
+				( new InputNumberField() )
+				->set_label('first')
+				->set_priority(1)
+			]
+		);
+
+		$expected = [
+			( new InputNumberField() )
+			->set_label('first')
+			->set_priority(1),
+			( new SelectField() )
+			->set_label('second')
+			->set_priority(5),
+			( new SelectField() )
+			->set_label('third')
+			->set_priority(7),
+			( new InputTextField() )
+			->set_label('fourth')
+		];
+
+		self::assertEquals($expected, $this->form->get_fields());
+	}
+
+	public function test_should_return_fields_by_adding_order_if_no_priority_set() {
+		$this->form->add_fields(
+			[
+				( new InputTextField() )
+					->set_label('first'),
+				( new SelectField() )
+					->set_label('second'),
+				( new InputNumberField() )
+					->set_label('third'),
+			]
+		);
+
+		$expected = [
+			( new InputTextField() )
+				->set_label('first'),
+			( new SelectField() )
+				->set_label('second'),
+			( new InputNumberField() )
+				->set_label('third'),
+		];
+
+		self::assertEquals($expected, $this->form->get_fields());
+	}
+}
diff --git a/tests/integration/bootstrap.php b/tests/integration/bootstrap.php
index a422fd9c7fea652a15b2aced0b2e6134a5590201..bbe288972d79fa0587ace00864945146bbe5716e 100644
--- a/tests/integration/bootstrap.php
+++ b/tests/integration/bootstrap.php
@@ -15,14 +15,4 @@ if ( getenv( 'PLUGIN_PATH' ) !== false ) {
 	define( 'PLUGIN_PATH', getenv( 'PLUGIN_PATH' ) );
 } else {
 	define( 'PLUGIN_PATH', __DIR__ . DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR );
-}
-
-require_once( getenv( 'WP_DEVELOP_DIR' ) . '/tests/phpunit/includes/functions.php' );
-
-tests_add_filter( 'muplugins_loaded', function () {
-}, 100 );
-
-putenv('WP_TESTS_DIR=' . getenv( 'WP_DEVELOP_DIR' ) . '/tests/phpunit');
-require_once( getenv( 'WC_DEVELOP_DIR' ) . '/tests/bootstrap.php' );
-
-do_action('plugins_loaded');
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/tests/unit/TestForm.php b/tests/unit/TestForm.php
deleted file mode 100644
index a0702cfbc8f04ad8e35bf54a51077f28c3876e71..0000000000000000000000000000000000000000
--- a/tests/unit/TestForm.php
+++ /dev/null
@@ -1,58 +0,0 @@
-<?php
-
-namespace Tests;
-
-use WPDesk\Forms\Form\AbstractForm;
-
-class TestForm extends \PHPUnit\Framework\TestCase
-{
-
-	const FORM1_ID                = 'test_form';
-	const FORM1_FORM_DATA         = [ 'test' => true ];
-	const FORM1_UPDATED_FORM_DATA = [ 'test666' => true ];
-
-	private $form;
-
-	protected function setUp(){
-		// Create a new instance from the Abstract Class
-		$this->form = $this->getMockBuilder( AbstractForm::class )
-		                   ->enableOriginalConstructor()
-		                   ->setMethods(['get_form_id'])
-		                   ->getMockForAbstractClass();
-		$this->form->method( 'get_form_id' )->willReturn( self::FORM1_ID );
-		$this->form->method( 'create_form_data' )->willReturn( self::FORM1_FORM_DATA );
-	}
-
-	protected function getForm(){
-		return clone $this->form;
-	}
-
-    /**
-     * Test getting form id.
-     */
-    public function testFormId()
-    {
-    	$form = $this->getForm();
-	    $this->assertEquals(self::FORM1_ID, $form->get_form_id());
-    }
-
-	/**
-	 * Test getting form data.
-	 */
-	public function testFormData()
-	{
-		$form = $this->getForm();
-		$this->assertSame( self::FORM1_FORM_DATA, $form->get_form_data());
-	}
-
-	/**
-	 * Test updated form data.
-	 */
-	public function testUpdatedFormData()
-	{
-		$form = $this->getForm();
-
-		$form->update_form_data( self::FORM1_UPDATED_FORM_DATA );
-		$this->assertSame( array_merge( self::FORM1_FORM_DATA, self::FORM1_UPDATED_FORM_DATA ), $form->get_form_data());
-	}
-}
\ No newline at end of file
diff --git a/tests/unit/TestFormCollection.php b/tests/unit/TestFormCollection.php
deleted file mode 100644
index 67bd0ae7b5273b7e7c43b8633cbe988a64a72355..0000000000000000000000000000000000000000
--- a/tests/unit/TestFormCollection.php
+++ /dev/null
@@ -1,141 +0,0 @@
-<?php
-
-namespace Tests;
-
-use WPDesk\Forms\Form\AbstractForm;
-use WPDesk\Forms\Form\FormsCollection;
-
-class TestFormCollection extends \PHPUnit\Framework\TestCase {
-
-	const FORM1_ID = 'test_form';
-	const FORM2_ID = 'test_form2';
-
-	const FORM1_FORM_DATA = [ 'test' => true ];
-	const FORM2_FORM_DATA = [ 'test2' => 'potato' ];
-
-	const FORM1_PREFIXED_FORM_DATA = [ 'test_form_test' => true ];
-	const FORM1_UPDATED_FORM_DATA  = [ 'test666' => true ];
-
-
-	private $formConditionalTrue;
-	private $formConditionalFalse;
-
-	protected function setUp() {
-		$this->formConditionalTrue = $this->getMockBuilder( AbstractForm::class )
-		                                  ->enableOriginalConstructor()
-		                                  ->setMethods( [ 'get_form_id', 'is_active' ] )
-		                                  ->getMockForAbstractClass();
-		$this->formConditionalTrue->method( 'get_form_id' )->willReturn( self::FORM1_ID );
-		$this->formConditionalTrue->method( 'is_active' )->willReturn( true );
-		$this->formConditionalTrue->method( 'create_form_data' )->willReturn( self::FORM1_FORM_DATA );
-
-		$this->formConditionalFalse = $this->getMockBuilder( AbstractForm::class )
-		                                   ->enableOriginalConstructor()
-		                                   ->setMethods( [ 'get_form_id', 'is_active' ] )
-		                                   ->getMockForAbstractClass();
-		$this->formConditionalFalse->method( 'get_form_id' )->willReturn( self::FORM2_ID );
-		$this->formConditionalFalse->method( 'is_active' )->willReturn( false );
-		$this->formConditionalFalse->method( 'create_form_data' )->willReturn( self::FORM2_FORM_DATA );
-	}
-
-	protected function getFormConditionalTrue() {
-		return clone $this->formConditionalTrue;
-	}
-
-	protected function getFormConditionalFalse() {
-		return clone $this->formConditionalFalse;
-	}
-
-	/**
-	 * Test adding and checking single form.
-	 */
-	public function testIfFormExists() {
-		$collection = new FormsCollection();
-		$collection->add_form( $this->getFormConditionalTrue() );
-
-		$this->assertTrue( $collection->is_form_exists( self::FORM1_ID ) );
-	}
-
-	/**
-	 * Test adding and checking multiple forms.
-	 */
-	public function testIfFormsExists() {
-		$collection = new FormsCollection();
-		$collection->add_forms( [
-			$this->getFormConditionalTrue(),
-			$this->getFormConditionalFalse(),
-		] );
-
-		$this->assertTrue( $collection->is_form_exists( self::FORM1_ID ) );
-		$this->assertTrue( $collection->is_form_exists( self::FORM2_ID ) );
-	}
-
-	/**
-	 * Test getting single form. AbstractForm object is expected
-	 */
-	public function testGettingExistingForm() {
-		$collection = new FormsCollection();
-		$collection->add_form( $this->getFormConditionalTrue() );
-
-		$this->assertInstanceOf(
-			AbstractForm::class,
-			$collection->get_form( self::FORM1_ID )
-		);
-	}
-
-	/**
-	 * Test getting not existing single form.
-	 */
-	public function testGettingNotExistingForm() {
-		$collection = new FormsCollection();
-		$collection->add_form( $this->getFormConditionalTrue() );
-
-		$this->expectException( \OutOfRangeException::class );
-		$collection->get_form( '123456' );
-	}
-
-	/**
-	 * Test returned data.
-	 */
-	public function testReturnedFormsData() {
-		$collection = new FormsCollection();
-		$collection->add_forms( [
-			$this->getFormConditionalTrue(),
-			$this->getFormConditionalFalse(),
-		] );
-
-		$this->assertSame( self::FORM1_FORM_DATA, $collection->get_forms_data() );
-	}
-
-	/**
-	 * Test returned prefixed data.
-	 */
-	public function testReturnedPrefixedFormsData() {
-		$collection = new FormsCollection();
-		$collection->add_forms( [
-			$this->getFormConditionalTrue(),
-			$this->getFormConditionalFalse(),
-		] );
-
-		$this->assertSame( self::FORM1_PREFIXED_FORM_DATA, $collection->get_forms_data( true ) );
-
-
-	}
-
-	/**
-	 * Test returned updated data.
-	 */
-	public function testReturnedUpdatedFormsData() {
-		$collection = new FormsCollection();
-		$collection->add_forms( [
-			$this->getFormConditionalTrue(),
-			$this->getFormConditionalFalse(),
-		] );
-
-		$form = $collection->get_form( self::FORM1_ID );
-		$form->update_form_data( self::FORM1_UPDATED_FORM_DATA );
-		$this->assertSame( array_merge( self::FORM1_FORM_DATA, self::FORM1_UPDATED_FORM_DATA ), $collection->get_forms_data() );
-
-	}
-
-}
\ No newline at end of file