diff --git a/README.md b/README.md
index 1db7678a5b8cea8fe61d86a77943f19286c0eb4a..06702bf6be3f5cdda326d517e693e8d9d184fd18 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,310 @@
-wp-forms
\ No newline at end of file
+[![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
+===================================================
+
+
+## Requirements
+
+PHP 5.6 or later.
+
+## Composer
+
+You can install the bindings via [Composer](http://getcomposer.org/). Run the following command:
+
+```bash
+composer require --dev wpdesk/wp-forms
+```
+
+To use the bindings, use Composer's [autoload](https://getcomposer.org/doc/01-basic-usage.md#autoloading):
+
+```php
+require_once 'vendor/autoload.php';
+```
+
+## Usage
+
+Let's say we have an abstraction for settings tabs:
+```php
+interface SettingsTab {
+	/**
+	 * Slug name used for unique url and settings in db.
+	 *
+	 * @return string
+	 */
+	public static function get_tab_slug();
+
+	/**
+	 * Tab name to show on settings page.
+	 *
+	 * @return string
+	 */
+	public function get_tab_name();
+
+	/**
+	 * Render tab content and return it as string.
+	 *
+	 * @param Renderer $renderer
+	 *
+	 * @return string
+	 */
+	public function render( Renderer $renderer );
+
+	/**
+	 * Use to set settings from database or defaults.
+	 *
+	 * @param array|ContainerInterface $data Data to render.
+	 *
+	 * @return void
+	 */
+	public function set_data( $data );
+
+	/**
+	 * Use to handle request data from POST.
+	 * Data in POST request should be prefixed with slug.
+	 * For example if slug is 'stefan' and the input has name 'color' and value 'red' then the data should be sent as
+	 * $_POST = [ 'stefan' => [ 'color' => 'red' ] ].
+	 *
+	 * @param array $request Data retrieved from POST request.
+	 *
+	 * @return void
+	 */
+	public function handle_request( $request );
+
+	/**
+	 * Returns valid data from Tab. Can be used after ::handle_request or ::set_data.
+	 *
+	 * @return array
+	 */
+	public function get_data();
+}
+```
+
+And then abstract implementation for most of these methods:
+
+```php
+abstract class FieldSettingsTab implements SettingsTab {
+	/** @var FormWithFields */
+	private $form;
+
+	/**
+	 * @return Field[]
+	 */
+	abstract protected function get_fields();
+
+	/**
+	 * @return FormWithFields
+	 */
+	protected function get_form() {
+		if ( $this->form === null ) {
+			$fields     = $this->get_fields();
+			$this->form = new FormWithFields( $fields, static::get_tab_slug() );
+		}
+
+		return $this->form;
+	}
+
+	public function render( Renderer $renderer) {
+		return $this->get_form()->render_form( $renderer );
+	}
+
+	public function set_data( $data ) {
+		$this->get_form()->set_data( $data );
+	}
+
+	public function handle_request( $request ) {
+		$this->get_form()->handle_request( $request );
+	}
+
+	public function get_data() {
+		return $this->get_form()->get_data();
+	}
+}
+```
+
+Then we can create a settings tab that looks like that:
+
+```php
+final class GeneralSettings extends FieldSettingsTab {
+	/**
+	 * @inheritDoc
+	 */
+	protected function get_fields() {
+		return [
+			( new CheckboxField() )
+				->set_label( __( 'Subscribe on checkout', 'some-text-domain' ) )
+				->set_description( __( 'Ask customers to subscribe to your mailing list on checkout.',
+					'some-text-domain' ) )
+				->set_description_tip( __( 'Setup Mailchimp or other email service add-on first.',
+					'some-text-domain' ) )
+				->set_name( 'wc_settings_sm_subscribe_on_checkout' ),
+
+			( new CheckboxField() )
+				->set_label( __( 'Help Icon', 'some-text-domain' ) )
+				->set_sublabel( __( 'Disable help icon', 'some-text-domain' ) )
+				->set_description( __( 'Help icon shows only on pages with help articles and ability to ask for help. If you do not want the help icon to display, you can entirely disable it here.',
+					'some-text-domain' ) )
+				->set_name( 'disable_beacon' ),
+
+			( new CheckboxField() )
+				->set_label( __( 'Usage Data', 'some-text-domain' ) )
+				->set_sublabel( __( 'Enable', 'some-text-domain' ) )
+				->set_description( sprintf( __( 'Help us improve Application and allow us to collect insensitive plugin usage data, %sread more%s.',
+					'some-text-domain' ), '<a href="' . TrackerNotices::USAGE_DATA_URL . '" target="_blank">',
+					'</a>' ) )
+				->set_name( 'wpdesk_tracker_agree' ),
+
+			( new SubmitField() )
+				->set_name( 'save' )
+				->set_label( __( 'Save changes', 'some-text-domain' ) )
+				->add_class( 'button-primary' )
+		];
+	}
+
+	/**
+	 * @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:
+
+```php
+/**
+ * Adds settings to the menu and manages how and what is shown on the settings page.
+ */
+final class Settings {
+	/**
+	 * Save POST tab data. Before render.
+	 *
+	 * @return void
+	 */
+	public function save_settings_action() {
+		if ( isset( $_GET['page'] ) && $_GET['page'] !== self::$settings_slug ) {
+			return;
+		}
+		$tab            = $this->get_active_tab();
+		$data_container = self::get_settings_persistence( $tab::get_tab_slug() );
+		if ( ! empty( $_POST ) && isset( $_POST[ $tab::get_tab_slug() ] ) ) {
+			$tab->handle_request( $_POST[ $tab::get_tab_slug() ] );
+			$this->save_tab_data( $tab, $data_container );
+
+			new Notice( __( 'Your settings have been saved.', 'text-domain' ),
+				Notice::NOTICE_TYPE_SUCCESS );
+		} else {
+			$tab->set_data( $data_container );
+		}
+	}
+
+	/**
+	 * Render
+	 *
+	 * @return void
+	 */
+	public function render_page_action() {
+		$tab      = $this->get_active_tab();
+		$renderer = $this->get_renderer();
+		echo $renderer->render( 'menu', [
+			'base_url'   => self::get_url(),
+			'menu_items' => $this->get_tabs_menu_items(),
+			'selected'   => $this->get_active_tab()->get_tab_slug()
+		] );
+		echo $tab->render( $renderer );
+		echo $renderer->render( 'footer' );
+	}
+
+	/**
+	 * @return SettingsTab
+	 */
+	private function get_active_tab() {
+		$selected_tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : null;
+		$tabs         = $this->get_settings_tabs();
+		if ( ! empty( $selected_tab ) && isset( $tabs[ $selected_tab ] ) ) {
+			return $tabs[ $selected_tab ];
+		}
+
+		return reset( $tabs );
+	}
+
+	/**
+	 * @return SettingsTab[]
+	 */
+	private function get_settings_tabs() {
+		static $tabs = [];
+		if ( empty( $tabs ) ) {
+			$tabs = [
+				GeneralSettings::get_tab_slug()   => new GeneralSettings(),
+				SomeOtherSettings::get_tab_slug() => new SomeOtherSettings()
+			];
+		}
+
+		return $tabs;
+	}
+
+	/**
+	 * Returns writable container with saved settings.
+	 *
+	 * @param string $tab_slug Unique slug of a settings tab.
+	 *
+	 * @return PersistentContainer
+	 */
+	public static function get_settings_persistence( $tab_slug ) {
+		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();
+		array_walk( $tab_data, static function ( $value, $key ) use ( $container ) {
+			if ( ! empty( $key ) ) {
+				$container->set( $key, $value );
+			}
+		} );
+	}
+
+	/**
+	 * @return \WPDesk\View\Renderer\Renderer
+	 */
+	private function get_renderer() {
+		return new SimplePhpRenderer( new DefaultFormFieldResolver() );
+	}
+
+	/**
+	 * @return string[]
+	 */
+	private function get_tabs_menu_items() {
+		$menu_items = [];
+
+		foreach ( $this->get_settings_tabs() as $tab ) {
+			$menu_items[ $tab::get_tab_slug() ] = $tab->get_tab_name();
+		}
+
+		return $menu_items;
+	}
+}
+```
+
+
+## Project documentation
+
+PHPDoc: https://wpdesk.gitlab.io/wp-forms/index.html  
\ No newline at end of file
diff --git a/composer.json b/composer.json
index 7959928c50ef42cda67ac2793d4e0dc277b3e2c4..cf1e200c7d3a8712bae0a0841173a9524139c660 100644
--- a/composer.json
+++ b/composer.json
@@ -4,12 +4,18 @@
     {
       "name": "Marcin",
       "email": "marcin@wpdesk.pl"
+    },
+    {
+      "name": "Krzysiek",
+      "email": "krzysiek@wpdesk.pl"
     }
   ],
   "require": {
     "php": ">=5.6",
     "ext-curl": "*",
-    "ext-json": "*"
+    "ext-json": "*",
+    "wpdesk/wp-persistence": "^2.0-beta",
+    "wpdesk/wp-view": "^1.1"
   },
   "require-dev": {
     "phpunit/phpunit": "<7",
diff --git a/src/ContainerForm.php b/src/ContainerForm.php
new file mode 100644
index 0000000000000000000000000000000000000000..1d056b6cacd598007ecc177b29be31b4d7770ed0
--- /dev/null
+++ b/src/ContainerForm.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace WPDesk\Forms;
+
+
+use Psr\Container\ContainerInterface;
+use WPDesk\Persistence\PersistentContainer;
+
+/**
+ * Persistent container support for forms.
+ *
+ * @package WPDesk\Forms
+ */
+interface ContainerForm {
+	/**
+	 * @param ContainerInterface $data
+	 *
+	 * @return void
+	 */
+	public function set_data( $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
new file mode 100644
index 0000000000000000000000000000000000000000..cb20d5824a44f3890c8c48007c9b0d6bb04be1c4
--- /dev/null
+++ b/src/Escaper.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace WPDesk\Forms;
+
+interface Escaper {
+	/**
+	 * @param mixed $value
+	 *
+	 * @return string
+	 */
+	public function escape( $value );
+}
diff --git a/src/Field.php b/src/Field.php
new file mode 100644
index 0000000000000000000000000000000000000000..e3a26e94326ac2ff8dd0b42da0cbe702a27ce41a
--- /dev/null
+++ b/src/Field.php
@@ -0,0 +1,141 @@
+<?php
+
+namespace WPDesk\Forms;
+
+/**
+ * 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.
+ *
+ * The: Validation, Serialization, Sanitization features are provided trough delegated classes (get_validator, get_serializer ...)
+ *
+ * @package WPDesk\Forms
+ */
+interface Field {
+	/** @return string */
+	public function get_name();
+
+	/** @return mixed */
+	public function get_default_value();
+
+	/** @return string */
+	public function get_template_name();
+
+	/**
+	 * When this field is used on form this field will force it's own template.
+	 *
+	 * return bool
+	 */
+	public function should_override_form_template();
+
+	/**
+	 * HTML label.
+	 *
+	 * @return string
+	 */
+	public function get_label();
+
+	/** bool */
+	public function has_label();
+
+	/**
+	 * Description for field. It can be shown near the field.
+	 *
+	 * @return string
+	 */
+	public function get_description();
+
+	/**
+	 * Additional field description that should be shown in optional hover tip.
+	 *
+	 * @return string
+	 */
+	public function get_description_tip();
+
+	/** @return bool */
+	public function has_description_tip();
+
+	/** @return bool */
+	public function has_description();
+
+	/**
+	 * @return bool
+	 */
+	public function is_readonly();
+
+	/** @return bool */
+	public function is_disabled();
+
+	/** @return string */
+	public function get_id();
+
+	/** @bool */
+	public function is_required();
+
+	/** @return bool */
+	public function has_placeholder();
+
+	/** @return string */
+	public function get_placeholder();
+
+	/**
+	 * @param string[] $except
+	 *
+	 * @return string[] name->value
+	 */
+	public function get_attributes( $except = [] );
+
+	/**
+	 * @param string $name
+	 * @param string $default
+	 *
+	 * @return string
+	 */
+	public function get_attribute( $name, $default = null );
+
+	/** @return bool */
+	public function is_attribute_set( $name );
+
+	/**
+	 * @param string $name
+	 *
+	 * @return string
+	 */
+	public function get_meta_value( $name );
+
+	/** @return bool */
+	public function is_meta_value_set( $name );
+
+	/**
+	 * @return string
+	 */
+	public function get_classes();
+
+	/** bool */
+	public function has_classes();
+
+	/** @return bool */
+	public function is_class_set( $name );
+
+	/**
+	 * @return mixed
+	 */
+	public function get_possible_values();
+
+	/**
+	 * @return bool
+	 */
+	public function is_multiple();
+
+	/**
+	 * @return Validator
+	 */
+	public function get_validator();
+
+	/**
+	 * @return Sanitizer
+	 */
+	public function get_sanitizer();
+
+	/** @return Serializer */
+	public function get_serializer();
+}
diff --git a/src/Field/BasicField.php b/src/Field/BasicField.php
new file mode 100644
index 0000000000000000000000000000000000000000..02004ff3148c313352a7d34173cd555c49fb5478
--- /dev/null
+++ b/src/Field/BasicField.php
@@ -0,0 +1,298 @@
+<?php
+
+namespace WPDesk\Forms\Field;
+
+use WPDesk\Forms\Field;
+use WPDesk\Forms\Sanitizer\NoSanitize;
+use WPDesk\Forms\Serializer;
+use WPDesk\Forms\Serializer\NoSerialize;
+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 {
+
+	/** @var array[] */
+	protected $attributes;
+
+	/** @var array[] */
+	protected $meta;
+
+	protected $default_value;
+
+	public function __construct() {
+		$this->meta['class'] = [];
+	}
+
+	public function get_label() {
+		return $this->meta['label'];
+	}
+
+	/**
+	 * @param string $value
+	 *
+	 * @return $this
+	 */
+	public function set_label( $value ) {
+		$this->meta['label'] = $value;
+
+		return $this;
+	}
+
+	public function get_description_tip() {
+		return $this->meta['description_tip'];
+	}
+
+	public function has_description_tip() {
+		return isset( $this->meta['description_tip'] );
+	}
+
+	public function should_override_form_template() {
+		return isset( $this->attributes['overrite_template'] ) ? $this->attributes['overrite_template'] : false;
+	}
+
+	public function get_description() {
+		return $this->meta['description'];
+	}
+
+	public function has_label() {
+		return isset( $this->meta['label'] );
+	}
+
+	public function has_description() {
+		return isset( $this->meta['description'] );
+	}
+
+	public function set_description( $value ) {
+		$this->meta['description'] = $value;
+
+		return $this;
+	}
+
+	public function set_description_tip( $value ) {
+		$this->meta['description_tip'] = $value;
+
+		return $this;
+	}
+
+	/**
+	 * @return array
+	 *
+	 * @deprecated not sure if needed. TODO: Check later.
+	 */
+	public function get_type() {
+		return $this->attributes['type'];
+	}
+
+	/**
+	 * @param string $value
+	 *
+	 * @return $this
+	 */
+	public function set_placeholder( $value ) {
+		$this->meta['placeholder'] = $value;
+
+		return $this;
+	}
+
+	public function has_placeholder() {
+		return isset( $this->meta['placeholder'] );
+	}
+
+	public function get_placeholder() {
+		return $this->meta['placeholder'];
+	}
+
+	/**
+	 * @param string $name
+	 *
+	 * @return $this
+	 */
+	public function set_name( $name ) {
+		$this->attributes['name'] = $name;
+
+		return $this;
+	}
+
+	public function get_attributes( $except = [] ) {
+		return array_filter( $this->attributes, function ( $value, $key ) use ( $except ) {
+			return ! in_array( $key, $except );
+		}, ARRAY_FILTER_USE_BOTH );
+	}
+
+	public function get_meta_value( $name ) {
+		return $this->meta[ $name ];
+	}
+
+	public function get_classes() {
+		return implode( ' ', $this->meta['class'] );
+	}
+
+	public function has_classes() {
+		return ! empty( $this->meta['class'] );
+	}
+
+	public function get_possible_values() {
+		return isset( $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() );
+	}
+
+	public function get_name() {
+		return $this->attributes['name'];
+	}
+
+	public function is_multiple() {
+		return isset( $this->attributes['multiple'] ) ? $this->attributes['multiple'] : false;
+	}
+
+	/**
+	 * @return $this
+	 */
+	public function set_disabled() {
+		$this->attributes['disabled'] = true;
+
+		return $this;
+	}
+
+	public function is_disabled() {
+		return isset( $this->attributes['disabled'] ) ? $this->attributes['disabled'] : false;
+	}
+
+	/**
+	 * @return $this
+	 */
+	public function set_readonly() {
+		$this->attributes['readonly'] = true;
+
+		return $this;
+	}
+
+	public function is_readonly() {
+		return isset( $this->attributes['readonly'] ) ? $this->attributes['readonly'] : false;
+	}
+
+	/**
+	 * @return $this
+	 */
+	public function set_required() {
+		$this->meta['required'] = true;
+
+		return $this;
+	}
+
+	/**
+	 * @param string $class_name
+	 *
+	 * @return $this
+	 */
+	public function add_class( $class_name ) {
+		$this->meta['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 ] );
+
+		return $this;
+	}
+
+	/**
+	 * @param $name
+	 * @param $value
+	 *
+	 * @return $this
+	 */
+	public function set_attribute( $name, $value ) {
+		$this->attributes[ $name ] = $value;
+
+		return $this;
+	}
+
+	public function unset_attribute( $name ) {
+		unset( $this->attributes[ $name ] );
+
+		return $this;
+	}
+
+	public function is_attribute_set( $name ) {
+		return isset( $this->attributes[ $name ] );
+	}
+
+	public function is_meta_value_set( $name ) {
+		return isset( $this->meta[ $name ] );
+	}
+
+	public function is_class_set( $name ) {
+		return isset( $this->meta['class'][ $name ] );
+	}
+
+	public function get_default_value() {
+		return $this->default_value;
+	}
+
+	/**
+	 * @param string $value
+	 *
+	 * @return $this
+	 */
+	public function set_default_value( $value ) {
+		$this->default_value = $value;
+
+		return $this;
+	}
+
+	/**
+	 * @return ChainValidator
+	 */
+	public function get_validator() {
+		$chain = new ChainValidator();
+		if ( $this->is_required() ) {
+			$chain->attach( new RequiredValidator() );
+		}
+
+		return $chain;
+	}
+
+	public function is_required() {
+		return isset( $this->meta['required'] ) ? $this->meta['required'] : false;
+	}
+
+	public function get_attribute( $name, $default = null ) {
+		return isset( $this->attributes[ $name ] ) ?: $default;
+	}
+
+	public function get_sanitizer() {
+		return new NoSanitize();
+	}
+
+	/**
+	 * @return Serializer
+	 */
+	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;
+
+		return $this;
+	}
+}
diff --git a/src/Field/CheckboxField.php b/src/Field/CheckboxField.php
new file mode 100644
index 0000000000000000000000000000000000000000..705c4fe2f97e7f895ef1c58b4534c1465357a2ee
--- /dev/null
+++ b/src/Field/CheckboxField.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace WPDesk\Forms\Field;
+
+
+class CheckboxField extends BasicField {
+	const VALUE_TRUE = 'yes';
+	const VALUE_FALSE = 'no';
+
+	public function __construct() {
+		parent::__construct();
+		$this->set_attribute( 'type', 'checkbox' );
+	}
+
+	public function get_template_name() {
+		return 'input-checkbox';
+	}
+
+	public function get_sublabel() {
+		return $this->meta['sublabel'];
+	}
+
+	public function set_sublabel( $value ) {
+		$this->meta['sublabel'] = $value;
+
+		return $this;
+	}
+
+	public function has_sublabel() {
+		return isset( $this->meta['sublabel'] );
+	}
+}
diff --git a/src/Field/Header.php b/src/Field/Header.php
new file mode 100644
index 0000000000000000000000000000000000000000..491d99c9e86981ff619da27f21786cd2830ee11e
--- /dev/null
+++ b/src/Field/Header.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace WPDesk\Forms\Field;
+
+class Header extends NoValueField {
+	public function __construct() {
+		parent::__construct();
+		$this->meta['header_size'] = '';
+	}
+
+	public function get_template_name() {
+		return 'header';
+	}
+
+	public function should_override_form_template() {
+		return true;
+	}
+
+	public function set_header_size( $value ) {
+		$this->meta['header_size'] = $value;
+
+		return $this;
+	}
+}
diff --git a/src/Field/HiddenField.php b/src/Field/HiddenField.php
new file mode 100644
index 0000000000000000000000000000000000000000..51b78080d7e9533b28f8ea54c362158971633223
--- /dev/null
+++ b/src/Field/HiddenField.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace WPDesk\Forms\Field;
+
+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_sanitizer() {
+		return new TextFieldSanitizer();
+	}
+
+	public function get_template_name() {
+		return 'input-hidden';
+	}
+}
diff --git a/src/Field/InputTextField.php b/src/Field/InputTextField.php
new file mode 100644
index 0000000000000000000000000000000000000000..2070412379ea091eeb87a4ca9b3994bbe7acaf9b
--- /dev/null
+++ b/src/Field/InputTextField.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace WPDesk\Forms\Field;
+
+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() {
+		return new TextFieldSanitizer();
+	}
+
+	public function get_template_name() {
+		return 'input-text';
+	}
+}
diff --git a/src/Field/NoOnceField.php b/src/Field/NoOnceField.php
new file mode 100644
index 0000000000000000000000000000000000000000..0b6eeafd10f30358cfd7f29e619a1b9d6fa28d7f
--- /dev/null
+++ b/src/Field/NoOnceField.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace WPDesk\Forms\Field;
+
+use WPDesk\Forms\Validator\NonceValidator;
+
+class NoOnceField extends BasicField {
+
+	public function __construct( $action_name ) {
+		parent::__construct();
+		$this->meta['action'] = $action_name;
+	}
+
+	public function get_validator() {
+		return new NonceValidator( $this->get_meta_value( 'action' ) );
+	}
+
+	public function get_template_name() {
+		return 'noonce';
+	}
+}
diff --git a/src/Field/NoValueField.php b/src/Field/NoValueField.php
new file mode 100644
index 0000000000000000000000000000000000000000..830f47ef4fda51d95d0260afe23e3f232d0ef4e2
--- /dev/null
+++ b/src/Field/NoValueField.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace WPDesk\Forms\Field;
+
+/**
+ * Base class for Fields that can show itself on form but cannot process any value.
+ *
+ * @package WPDesk\Forms
+ */
+abstract class NoValueField extends BasicField {
+	public function get_name() {
+		return '';
+	}
+}
diff --git a/src/Field/Paragraph.php b/src/Field/Paragraph.php
new file mode 100644
index 0000000000000000000000000000000000000000..c62cb6b844d1846636e8aeaff8d2e1b69798ac03
--- /dev/null
+++ b/src/Field/Paragraph.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace WPDesk\Forms\Field;
+
+class Paragraph extends NoValueField {
+	public function get_template_name() {
+		return 'paragraph';
+	}
+
+	public function should_override_form_template() {
+		return true;
+	}
+}
diff --git a/src/Field/ProductSelect.php b/src/Field/ProductSelect.php
new file mode 100644
index 0000000000000000000000000000000000000000..19b14aebc1223e6db73bf34a1a1811ce7560aa3c
--- /dev/null
+++ b/src/Field/ProductSelect.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace WPDesk\Forms\Field;
+
+class ProductSelect extends SelectField {
+	public function __construct() {
+		parent::__construct();
+		$this->set_multiple();
+	}
+
+	public function get_template_name() {
+		return 'product-select';
+	}
+}
diff --git a/src/Field/RadioField.php b/src/Field/RadioField.php
new file mode 100644
index 0000000000000000000000000000000000000000..69630bf430c7ba352948f44dedb8b340b5df67e4
--- /dev/null
+++ b/src/Field/RadioField.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace WPDesk\Forms\Field;
+
+class RadioField extends BasicField {
+	public function get_template_name() {
+		return 'input-radio';
+	}
+}
diff --git a/src/Field/SelectField.php b/src/Field/SelectField.php
new file mode 100644
index 0000000000000000000000000000000000000000..0578379dc7d68c781025955f815eee3018db3d47
--- /dev/null
+++ b/src/Field/SelectField.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace WPDesk\Forms\Field;
+
+class SelectField extends BasicField {
+
+	public function get_template_name() {
+		return 'select';
+	}
+
+	public function set_options( $options ) {
+		$this->meta['possible_values'] = $options;
+
+		return $this;
+	}
+
+	public function set_multiple() {
+		$this->attributes['multiple'] = true;
+
+		return $this;
+	}
+}
diff --git a/src/Field/SubmitField.php b/src/Field/SubmitField.php
new file mode 100644
index 0000000000000000000000000000000000000000..c8e1f799d895ae49a10712439043a2b0e6a223e5
--- /dev/null
+++ b/src/Field/SubmitField.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace WPDesk\Forms\Field;
+
+class SubmitField extends NoValueField {
+	public function get_template_name() {
+		return 'input-submit';
+	}
+
+	public function get_type() {
+		return 'submit';
+	}
+
+	public function should_override_form_template() {
+		return true;
+	}
+}
diff --git a/src/Field/TextAreaField.php b/src/Field/TextAreaField.php
new file mode 100644
index 0000000000000000000000000000000000000000..94873b3f0b07dfa0f1a2ea033da60c69b92607b2
--- /dev/null
+++ b/src/Field/TextAreaField.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace WPDesk\Forms\Field;
+
+class TextAreaField extends BasicField {
+	public function __construct() {
+		parent::__construct();
+		$this->set_default_value( '' );
+	}
+
+	public function get_template_name() {
+		return 'textarea';
+	}
+}
diff --git a/src/Field/WooSelect.php b/src/Field/WooSelect.php
new file mode 100644
index 0000000000000000000000000000000000000000..b8d953808ca327e7ca4ef1ac0b305cd7410070bb
--- /dev/null
+++ b/src/Field/WooSelect.php
@@ -0,0 +1,15 @@
+<?php
+
+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() {
+		return 'woo-select';
+	}
+}
diff --git a/src/Field/WyswigField.php b/src/Field/WyswigField.php
new file mode 100644
index 0000000000000000000000000000000000000000..846c91995b607526eec4c0b60e8c8cbd945186e6
--- /dev/null
+++ b/src/Field/WyswigField.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace WPDesk\Forms\Field;
+
+class WyswigField extends BasicField {
+	public function __construct() {
+		parent::__construct();
+		$this->set_default_value( '' );
+	}
+
+	public function get_template_name() {
+		return 'wyswig';
+	}
+
+	public function should_override_form_template() {
+		return true;
+	}
+}
diff --git a/src/FieldProvider.php b/src/FieldProvider.php
new file mode 100644
index 0000000000000000000000000000000000000000..f7f38419e4f0973c6e63f3e72fd67f40e91fb2e8
--- /dev/null
+++ b/src/FieldProvider.php
@@ -0,0 +1,15 @@
+<?php
+
+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();
+}
diff --git a/src/FieldRenderer.php b/src/FieldRenderer.php
new file mode 100644
index 0000000000000000000000000000000000000000..37a356d6cfeb065577a6efd647769a954af7edf2
--- /dev/null
+++ b/src/FieldRenderer.php
@@ -0,0 +1,17 @@
+<?php
+
+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 = '' );
+}
diff --git a/src/FieldsDataReceiver.php b/src/FieldsDataReceiver.php
new file mode 100644
index 0000000000000000000000000000000000000000..9e2bdc9222ff58ff73be192494cbd9f60d96bb4b
--- /dev/null
+++ b/src/FieldsDataReceiver.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace WPDesk\Forms;
+
+use Psr\Container\ContainerInterface;
+
+/**
+ * Some field owners can receive and process field data.
+ * Probably should be used with FieldProvider interface.
+ *
+ * @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
new file mode 100644
index 0000000000000000000000000000000000000000..7450b2b1203750d62862f8af3b9980f3cf6b47e2
--- /dev/null
+++ b/src/Form.php
@@ -0,0 +1,80 @@
+<?php
+
+namespace WPDesk\Forms;
+
+use WPDesk\View\Renderer\Renderer;
+
+/**
+ * Abstraction layer for forms.
+ *
+ * @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();
+
+	/**
+	 * Whether form handle_request method was successfully executed.
+	 *
+	 * @return bool
+	 */
+	public function is_submitted();
+
+	/**
+	 * 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
+	 */
+	public function is_valid();
+
+	/**
+	 * 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.
+	 */
+	public function set_data( $data );
+
+	/**
+	 * 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 );
+
+	/**
+	 * Get data from form. Use after handle_request or set_data.
+	 *
+	 * @return array
+	 */
+	public function get_data();
+
+	/**
+	 * Get data from form. Use after handle_request or set_data.
+	 * The difference get_data is that here you will not get any objects and complex data types besides arrays.
+	 *
+	 * @return array
+	 */
+	public function get_normalized_data();
+
+	/**
+	 * Form if you ever need to have more than one form at once.
+	 *
+	 * @return string
+	 */
+	public function get_form_id();
+}
+
diff --git a/src/AbstractForm.php b/src/Form/AbstractForm.php
similarity index 89%
rename from src/AbstractForm.php
rename to src/Form/AbstractForm.php
index e389bff4da48d31d84f5074a4aafda457ac26195..b51a0e3661082a8018727991e80fd57ad7ba70ad 100644
--- a/src/AbstractForm.php
+++ b/src/Form/AbstractForm.php
@@ -1,12 +1,14 @@
 <?php
 
-namespace WPDesk\Forms;
+namespace WPDesk\Forms\Form;
 
 
 /**
- * Abstraction layer for forms.
+ * Old abstraction layer for forms.
  *
  * @package WPDesk\Forms
+ *
+ * @deprecated Use ony for backward compatibility with Forms 1.x
  */
 abstract class AbstractForm {
 
@@ -68,7 +70,7 @@ abstract class AbstractForm {
 	 * @return array
 	 */
 	public function get_prefixed_form_data() {
-		$array = $this->get_form_data();
+		$array   = $this->get_form_data();
 		$form_id = $this->get_form_id();
 
 		return array_combine(
diff --git a/src/Form/FormWithFields.php b/src/Form/FormWithFields.php
new file mode 100644
index 0000000000000000000000000000000000000000..490032c3b786832a680d1c9aec41d743c3249f7f
--- /dev/null
+++ b/src/Form/FormWithFields.php
@@ -0,0 +1,218 @@
+<?php
+
+namespace WPDesk\Forms\Form;
+
+use Psr\Container\ContainerInterface;
+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;
+
+class FormWithFields implements Form, ContainerForm, FieldProvider {
+	/**
+	 * Unique form_id.
+	 *
+	 * @var string
+	 */
+	protected $form_id = 'form';
+	/**
+	 * Updated data.
+	 *
+	 * @var array
+	 */
+	private $updated_data;
+	/**
+	 * Form fields.
+	 *
+	 * @var Field[]
+	 */
+	private $fields;
+
+	/**
+	 * FormWithFields constructor.
+	 *
+	 * @param array $fields Form fields.
+	 * @param string $form_id Unique form id.
+	 */
+	public function __construct( array $fields, $form_id = 'form' ) {
+		$this->fields       = $fields;
+		$this->form_id      = $form_id;
+		$this->updated_data = null;
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	public function is_submitted() {
+		return null !== $this->updated_data;
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	public function add_field( Field $field ) {
+		$this->fields[] = $field;
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	public function is_active() {
+		return true;
+	}
+
+	/**
+	 * Add more fields to form.
+	 *
+	 * @param Field[] $fields Field to add to form.
+	 */
+	public function add_fields( array $fields ) {
+		array_map( [ $this, 'add_field' ], $fields );
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	public function is_valid() {
+		foreach ( $this->fields as $field ) {
+			$field_value     = isset( $this->updated_data[ $field->get_name() ] ) ? $this->updated_data[ $field->get_name() ] : null;
+			$field_validator = $field->get_validator();
+			if ( ! $field_validator->is_valid( $field_value ) ) {
+				return false;
+			}
+		}
+
+		return true;
+	}
+
+	/**
+	 * Add array to update data.
+	 *
+	 * @param array|ContainerInterface $request new data to update.
+	 */
+	public function handle_request( $request = array() ) {
+		if ( $this->updated_data === null ) {
+			$this->updated_data = [];
+		}
+		foreach ( $this->fields as $field ) {
+			$data_key = $field->get_name();
+			if ( isset( $request[ $data_key ] ) ) {
+				$this->updated_data[ $data_key ] = $field->get_sanitizer()->sanitize( $request[ $data_key ] );
+			}
+		}
+	}
+
+	/**
+	 * 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.
+	 */
+	public function set_data( $data ) {
+		if ( is_array( $data ) ) {
+			$data = new ArrayContainer( $data );
+		}
+		foreach ( $this->fields as $field ) {
+			$data_key = $field->get_name();
+			if ( $data->has( $data_key ) ) {
+				try {
+					$this->updated_data[ $data_key ] = $data->get( $data_key );
+				} catch ( ElementNotExistsException $e ) {
+					$this->updated_data[ $data_key ] = false;
+				}
+			}
+		}
+	}
+
+	/**
+	 * Renders only fields without form.
+	 *
+	 * @param Renderer $renderer
+	 *
+	 * @return string
+	 */
+	public function render_fields( Renderer $renderer ) {
+		$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',
+				[
+					'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(),
+					'template_name' => $field->get_template_name(),
+				] );
+		}
+
+		return $content;
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	public function render_form( Renderer $renderer ) {
+		$content = $renderer->render( 'form-start', [
+			'method' => 'POST',
+			'action' => '',
+		] );
+		$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();
+			if ( ! isset( $this->updated_data[ $data_key ] ) ) {
+				$container->set( $data_key, $field->get_default_value() );
+			} else {
+				$container->set( $data_key, $this->updated_data[ $data_key ] );
+			}
+		}
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	public function get_data() {
+		$data = $this->updated_data;
+
+		foreach ( $this->get_fields() as $field ) {
+			$data_key = $field->get_name();
+			if ( ! isset( $data[ $data_key ] ) ) {
+				$data[ $data_key ] = $field->get_default_value();
+			}
+		}
+
+		return $data;
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	public function get_fields() {
+		return $this->fields;
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	public function get_form_id() {
+		return $this->form_id;
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	public function get_normalized_data() {
+		return $this->get_data();
+	}
+}
diff --git a/src/FormsCollection.php b/src/Form/FormsCollection.php
similarity index 84%
rename from src/FormsCollection.php
rename to src/Form/FormsCollection.php
index 4cded9f4c18345ba60d10bdb1cc9b791fa0da893..f2603a31e043719383c1f59a3385aa97b77ee272 100644
--- a/src/FormsCollection.php
+++ b/src/Form/FormsCollection.php
@@ -1,26 +1,42 @@
 <?php
 
-namespace WPDesk\Forms;
+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 AbstractForm[]
+	 * @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 AbstractForm[] $forms
+	 * @param Form[] $forms
 	 */
 	public function add_forms( array $forms = array() ) {
 		foreach ( $forms as $form_object ) {
@@ -31,7 +47,7 @@ class FormsCollection {
 	/**
 	 * Add form. If key is not unique throw exception.
 	 *
-	 * @param AbstractForm $form
+	 * @param Form $form
 	 *
 	 * @throws \OutOfBoundsException
 	 */
@@ -59,7 +75,7 @@ class FormsCollection {
 	 *
 	 * @param string $form_id
 	 *
-	 * @return AbstractForm
+	 * @return Form
 	 * @throws \OutOfRangeException
 	 */
 	public function get_form( $form_id ) {
diff --git a/src/Persistence/FieldPersistenceStrategy.php b/src/Persistence/FieldPersistenceStrategy.php
new file mode 100644
index 0000000000000000000000000000000000000000..092a01a61643fc5c0ce762a64a38c264f6637b90
--- /dev/null
+++ b/src/Persistence/FieldPersistenceStrategy.php
@@ -0,0 +1,54 @@
+<?php
+
+namespace WPDesk\Forms\Persistence;
+
+use Psr\Container\NotFoundExceptionInterface;
+use WPDesk\Forms\FieldProvider;
+use WPDesk\Persistence\PersistentContainer;
+
+/**
+ * Can save/load provided fields to/from PersistentContainer.
+ *
+ * @package WPDesk\Forms
+ */
+class FieldPersistenceStrategy {
+	/** @var PersistentContainer */
+	private $persistence;
+
+	public function __construct( PersistentContainer $persistence ) {
+		$this->persistence = $persistence;
+	}
+
+	/**
+	 * Save fields data.
+	 *
+	 * @param FieldProvider $fields_provider
+	 * @param array $data
+	 */
+	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 ] ) );
+		}
+	}
+
+	/**
+	 * Load fields data.
+	 *
+	 * @return array
+	 */
+	public function load_fields( FieldProvider $fields_provider ) {
+		$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 ) );
+			} catch ( NotFoundExceptionInterface $not_found ) {
+				// TODO: Logger
+//				LoggerFactory::get_logger()->info( "FieldPersistenceStrategy:: Field {$field_key} not found" );
+			}
+		}
+
+		return $data;
+	}
+}
diff --git a/src/Renderer/JsonNormalizedRenderer.php b/src/Renderer/JsonNormalizedRenderer.php
new file mode 100644
index 0000000000000000000000000000000000000000..175fe70cbab72312a118f294ed419b09d5bd0c0f
--- /dev/null
+++ b/src/Renderer/JsonNormalizedRenderer.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace WPDesk\Forms\Renderer;
+
+use WPDesk\Forms\FieldProvider;
+use WPDesk\Forms\FieldRenderer;
+
+/**
+ * Can render form fields as JSON.
+ *
+ * @package WPDesk\Forms\Renderer
+ */
+class JsonNormalizedRenderer implements FieldRenderer {
+	/**
+	 * @param FieldProvider $provider
+	 * @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 = '' ) {
+		$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_description() ) {
+				$rendered['description'] = $field->get_description();
+			}
+			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;
+			}
+			$rendered_fields[] = $rendered;
+		}
+
+		return $rendered_fields;
+	}
+}
diff --git a/src/Resolver/DefaultFormFieldResolver.php b/src/Resolver/DefaultFormFieldResolver.php
new file mode 100644
index 0000000000000000000000000000000000000000..3609a6973a3137ce3e99a227c42f85c6650a9212
--- /dev/null
+++ b/src/Resolver/DefaultFormFieldResolver.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace WPDesk\Forms\Resolver;
+
+use WPDesk\View\Renderer\Renderer;
+use WPDesk\View\Resolver\DirResolver;
+use WPDesk\View\Resolver\Resolver;
+
+/**
+ * Use with View to resolver form fields to default templates.
+ *
+ * @package WPDesk\Forms\Resolver
+ */
+class DefaultFormFieldResolver implements Resolver {
+	/** @var Resolver */
+	private $dir_resolver;
+
+	public function __construct() {
+		$this->dir_resolver = new DirResolver( __DIR__ . '/../../templates' );
+	}
+
+	public function resolve( $name, Renderer $renderer = null ) {
+		return $this->dir_resolver->resolve( $name, $renderer );
+	}
+
+}
\ No newline at end of file
diff --git a/src/Sanitizer.php b/src/Sanitizer.php
new file mode 100644
index 0000000000000000000000000000000000000000..a7883dcf74f7927763d47285764dea3471be9dad
--- /dev/null
+++ b/src/Sanitizer.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace WPDesk\Forms;
+
+interface Sanitizer {
+	/**
+	 * @param mixed $value
+	 *
+	 * @return string
+	 */
+	public function sanitize( $value );
+}
diff --git a/src/Sanitizer/CallableSanitizer.php b/src/Sanitizer/CallableSanitizer.php
new file mode 100644
index 0000000000000000000000000000000000000000..c0a28b9756d8d18a12be39d645e643d6d828fcf9
--- /dev/null
+++ b/src/Sanitizer/CallableSanitizer.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace WPDesk\Forms\Sanitizer;
+
+use WPDesk\Forms\Sanitizer;
+
+class CallableSanitizer implements Sanitizer {
+	private $callable;
+
+	public function __construct( $callable ) {
+		$this->callable = $callable;
+	}
+
+	public function sanitize( $value ) {
+		return call_user_func( $this->callable, $value );
+	}
+
+}
diff --git a/src/Sanitizer/NoSanitize.php b/src/Sanitizer/NoSanitize.php
new file mode 100644
index 0000000000000000000000000000000000000000..a3fe4a61160f130cae2821f2bf9faa8352d761c7
--- /dev/null
+++ b/src/Sanitizer/NoSanitize.php
@@ -0,0 +1,12 @@
+<?php
+
+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
new file mode 100644
index 0000000000000000000000000000000000000000..0a1bbcbca0f7f12580bfa0fb09ca78c3da9f82e4
--- /dev/null
+++ b/src/Sanitizer/TextFieldSanitizer.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace WPDesk\Forms\Sanitizer;
+
+use WPDesk\Forms\Sanitizer;
+
+class TextFieldSanitizer implements Sanitizer {
+	public function sanitize( $value ) {
+		return sanitize_text_field( $value );
+	}
+
+}
diff --git a/src/Serializer.php b/src/Serializer.php
new file mode 100644
index 0000000000000000000000000000000000000000..91054006581db530155966b9482962d72384761e
--- /dev/null
+++ b/src/Serializer.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace WPDesk\Forms;
+
+interface Serializer {
+	public function serialize( $value );
+
+	public function unserialize( $value );
+}
diff --git a/src/Serializer/NoSerialize.php b/src/Serializer/NoSerialize.php
new file mode 100644
index 0000000000000000000000000000000000000000..b29e88b9e23420a2c67b5bd2f10136d8f509edfa
--- /dev/null
+++ b/src/Serializer/NoSerialize.php
@@ -0,0 +1,16 @@
+<?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
new file mode 100644
index 0000000000000000000000000000000000000000..2adf327996bc3d2ad6f383a72a26642e9161cb92
--- /dev/null
+++ b/src/Serializer/ProductSelectSerializer.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace WPDesk\Forms\Serializer;
+
+use WPDesk\Forms\Serializer;
+
+/**
+ * When serializing product select data lets also write product names in keys.
+ *
+ * @package WPDesk\Forms\Serializer
+ */
+class ProductSelectSerializer implements Serializer {
+	/**
+	 * @param $value
+	 */
+	public function serialize( $value ) {
+		$products_with_names = [];
+		if ( is_array( $value ) ) {
+			foreach ( $value as $product_id ) {
+				$product = wc_get_product( $product_id );
+				if ( $product ) {
+					$name                         = $product->get_formatted_name();
+					$products_with_names[ $name ] = $product_id;
+				}
+			}
+		}
+
+		return $products_with_names;
+	}
+
+	public function unserialize( $value ) {
+		return $value;
+	}
+
+}
diff --git a/src/Serializer/SerializeSerializer.php b/src/Serializer/SerializeSerializer.php
new file mode 100644
index 0000000000000000000000000000000000000000..79d13f6fecfc1d300a4f5a174190b6f4d0e49c0f
--- /dev/null
+++ b/src/Serializer/SerializeSerializer.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace WPDesk\Forms\Serializer;
+
+use WPDesk\Forms\Serializer;
+
+class SerializeSerializer implements Serializer {
+	public function serialize( $value ) {
+		return serialize( $value );
+	}
+
+	public function unserialize( $value ) {
+		return unserialize( $value );
+	}
+
+}
diff --git a/src/Validator.php b/src/Validator.php
new file mode 100644
index 0000000000000000000000000000000000000000..d953702d23861c2af778c70b4027d8cfb211f793
--- /dev/null
+++ b/src/Validator.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace WPDesk\Forms;
+
+interface Validator {
+	/**
+	 * @param mixed $value
+	 *
+	 * @return bool
+	 */
+	public function is_valid( $value );
+
+	/**
+	 * @return string[]
+	 */
+	public function get_messages();
+}
diff --git a/src/Validator/ChainValidator.php b/src/Validator/ChainValidator.php
new file mode 100644
index 0000000000000000000000000000000000000000..d3549d566ed861b8ccd7caef78734a1e2dc321cb
--- /dev/null
+++ b/src/Validator/ChainValidator.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace WPDesk\Forms\Validator;
+
+use WPDesk\Forms\Validator;
+
+class ChainValidator implements Validator {
+	/** @var Validator[] */
+	private $validators;
+
+	private $messages;
+
+	public function __construct() {
+		$this->validators = [];
+		$this->messages   = [];
+	}
+
+	/**
+	 * @param Validator $validator
+	 *
+	 * @return $this
+	 */
+	public function attach( Validator $validator ) {
+		$this->validators[] = $validator;
+
+		return $this;
+	}
+
+	public function is_valid( $value ) {
+		$result   = true;
+		$messages = [ [] ];
+		foreach ( $this->validators as $validator ) {
+			if ( ! $validator->is_valid( $value ) ) {
+				$result     = false;
+				$messages[] = $validator->get_messages();
+			}
+		}
+		$this->messages = array_merge( ...$messages );
+
+		return $result;
+	}
+
+	public function get_messages() {
+		return $this->messages;
+	}
+
+}
diff --git a/src/Validator/NoValidateValidator.php b/src/Validator/NoValidateValidator.php
new file mode 100644
index 0000000000000000000000000000000000000000..dbc996646da8d0fb7d0128404abcb90669d61202
--- /dev/null
+++ b/src/Validator/NoValidateValidator.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace WPDesk\Forms\Validator;
+
+use WPDesk\Forms\Validator;
+
+class NoValidateValidator implements Validator {
+	public function is_valid( $value ) {
+		return true;
+	}
+
+	public function get_messages() {
+		return [];
+	}
+
+}
diff --git a/src/Validator/NonceValidator.php b/src/Validator/NonceValidator.php
new file mode 100644
index 0000000000000000000000000000000000000000..87682257b08d4c14525a80a4f30f3082d0ba6014
--- /dev/null
+++ b/src/Validator/NonceValidator.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace WPDesk\Forms\Validator;
+
+use WPDesk\Forms\Validator;
+
+class NonceValidator implements Validator {
+	private $action;
+
+	public function __construct( $action ) {
+		$this->action = $action;
+	}
+
+	public function is_valid( $value ) {
+		$valid = wp_verify_nonce( $value, $this->action );
+
+		return $valid;
+	}
+
+	public function get_messages() {
+		return [];
+	}
+
+}
diff --git a/src/Validator/RequiredValidator.php b/src/Validator/RequiredValidator.php
new file mode 100644
index 0000000000000000000000000000000000000000..016d803d86a95cf3c4c4dd3b6bf9a189c1edffa3
--- /dev/null
+++ b/src/Validator/RequiredValidator.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace WPDesk\Forms\Validator;
+
+use WPDesk\Forms\Validator;
+
+class RequiredValidator implements Validator {
+	public function is_valid( $value ) {
+		return $value !== null;
+	}
+
+	public function get_messages() {
+		return [];
+	}
+
+}
diff --git a/templates/form-end.php b/templates/form-end.php
new file mode 100644
index 0000000000000000000000000000000000000000..7484d6e1db1aba308d78afa7cd384d5f27d4a845
--- /dev/null
+++ b/templates/form-end.php
@@ -0,0 +1,14 @@
+</tbody>
+</table>
+</form>
+
+<script type="text/javascript">
+	(function($) {
+		$( '.tips, .help_tip, .woocommerce-help-tip' ).tipTip( {
+			'attribute': 'data-tip',
+			'fadeIn': 50,
+			'fadeOut': 50,
+			'delay': 200
+		} );
+	})(jQuery);
+</script>
diff --git a/templates/form-field.php b/templates/form-field.php
new file mode 100644
index 0000000000000000000000000000000000000000..971e53ba0447e37f1f3fcee0255f793797a29478
--- /dev/null
+++ b/templates/form-field.php
@@ -0,0 +1,30 @@
+<?php
+/**
+ * @var \WPDesk\Forms\Field $field
+ * @var \WPDesk\View\Renderer\Renderer $renderer
+ * @var string $name_prefix
+ * @var string $value
+ *
+ * @var string $template_name Real field template.
+ *
+ */
+?>
+
+<tr valign="top">
+	<?php if ( $field->has_label() ): ?>
+		<?php echo $renderer->render( 'form-label', [ 'field' => $field ] ); ?>
+	<?php endif; ?>
+
+	<td class="forminp">
+		<?php echo $renderer->render( $template_name, [
+			'field'       => $field,
+			'renderer'    => $renderer,
+			'name_prefix' => $name_prefix,
+			'value'       => $value,
+		] ); ?>
+
+		<?php if ( $field->has_description() ): ?>
+			<p class="description"><?php echo wp_kses_post( $field->get_description() ); ?></p>
+		<?php endif; ?>
+	</td>
+</tr>
diff --git a/templates/form-label.php b/templates/form-label.php
new file mode 100644
index 0000000000000000000000000000000000000000..703ed78e6909b409488e2697cb997c5dcb7ebe37
--- /dev/null
+++ b/templates/form-label.php
@@ -0,0 +1,14 @@
+<?php
+/**
+ * @var \WPDesk\Forms\Field $field
+ * @var string $name_prefix
+ * @var string $value
+ */
+?>
+<th class="titledesc" scope="row">
+	<label for="<?php echo esc_attr( $field->get_id() ); ?>"><?php echo esc_html( $field->get_label() ); ?>
+		<?php if ( $field->has_description_tip() ): ?>
+			<?php echo wc_help_tip($field->get_description_tip()); ?>
+		<?php endif ?>
+	</label>
+</th>
diff --git a/templates/form-start.php b/templates/form-start.php
new file mode 100644
index 0000000000000000000000000000000000000000..97ed3dfbcfed26ade3f337271c86684be6802dfd
--- /dev/null
+++ b/templates/form-start.php
@@ -0,0 +1,11 @@
+<?php
+/**
+ * @var string $method
+ * @var string $action
+ */
+?>
+<form class="wrap woocommerce" method="<?php echo esc_attr($method); ?>" action="<?php echo esc_attr($action); ?>">
+	<h2 style="display:none;"></h2><?php // All admin notices will be moved here by WP js ?>
+
+	<table class="form-table">
+		<tbody>
diff --git a/templates/header.php b/templates/header.php
new file mode 100644
index 0000000000000000000000000000000000000000..48b0f1160fb41cc8f5e66205a47e9fc2babb5e57
--- /dev/null
+++ b/templates/header.php
@@ -0,0 +1,19 @@
+<?php
+/**
+ * @var \WPDesk\Forms\Field $field
+ * @var string $name_prefix
+ * @var string $value
+ */
+
+$header_size = $field->get_meta_value( 'header_size' ) ?: '2';
+$classes     = $field->has_classes() ? 'class="' . $field->get_classes() . '"' : '';
+
+?>
+
+<?php if ( $field->has_label() ): ?>
+	<h<?php echo $header_size; ?> <?php echo $classes; ?>><?php echo esc_html( $field->get_label() ); ?></h<?php echo $header_size; ?>>
+<?php endif; ?>
+
+<?php if ( $field->has_description() ): ?>
+	<p <?php echo $classes; ?>><?php echo wp_kses_post( $field->get_description() ); ?></p>
+<?php endif; ?>
diff --git a/templates/input-checkbox.php b/templates/input-checkbox.php
new file mode 100644
index 0000000000000000000000000000000000000000..61dc39f5a6c06fa680a3969e7a53084c3e1bf0b1
--- /dev/null
+++ b/templates/input-checkbox.php
@@ -0,0 +1,18 @@
+<?php
+/**
+ * @var \WPDesk\Forms\Field $field
+ * @var \WPDesk\View\Renderer\Renderer $renderer
+ * @var string $name_prefix
+ * @var string $value
+ *
+ * @var string $template_name Real field template.
+ *
+ */
+?>
+
+<?php echo $renderer->render('input', [
+	'field' => $field,
+	'renderer' => $renderer,
+	'name_prefix' => $name_prefix,
+	'value' => $value,
+]); ?>
diff --git a/templates/input-hidden.php b/templates/input-hidden.php
new file mode 100644
index 0000000000000000000000000000000000000000..3effbd29c61a8eb271e5b199110318710978c54e
--- /dev/null
+++ b/templates/input-hidden.php
@@ -0,0 +1,17 @@
+<?php
+/**
+ * @var \WPDesk\Forms\Field $field
+ * @var \WPDesk\View\Renderer\Renderer $renderer
+ * @var string $name_prefix
+ * @var string $value
+ *
+ * @var string $template_name Real field template.
+ *
+ */
+?>
+<?php echo $renderer->render('input', [
+	'field' => $field,
+	'renderer' => $renderer,
+	'name_prefix' => $name_prefix,
+	'value' => $value,
+]); ?>
diff --git a/templates/input-radio.php b/templates/input-radio.php
new file mode 100644
index 0000000000000000000000000000000000000000..3effbd29c61a8eb271e5b199110318710978c54e
--- /dev/null
+++ b/templates/input-radio.php
@@ -0,0 +1,17 @@
+<?php
+/**
+ * @var \WPDesk\Forms\Field $field
+ * @var \WPDesk\View\Renderer\Renderer $renderer
+ * @var string $name_prefix
+ * @var string $value
+ *
+ * @var string $template_name Real field template.
+ *
+ */
+?>
+<?php echo $renderer->render('input', [
+	'field' => $field,
+	'renderer' => $renderer,
+	'name_prefix' => $name_prefix,
+	'value' => $value,
+]); ?>
diff --git a/templates/input-submit.php b/templates/input-submit.php
new file mode 100644
index 0000000000000000000000000000000000000000..5f676450462383025628b7c61a65355585efcb1f
--- /dev/null
+++ b/templates/input-submit.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ * @var \WPDesk\Forms\Field $field
+ * @var \WPDesk\View\Renderer\Renderer $renderer
+ * @var string $name_prefix
+ * @var string $value
+ *
+ * @var string $template_name Real field template.
+ *
+ */
+?>
+
+<tr>
+	<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 echo $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>
+</tr>
diff --git a/templates/input-text.php b/templates/input-text.php
new file mode 100644
index 0000000000000000000000000000000000000000..3effbd29c61a8eb271e5b199110318710978c54e
--- /dev/null
+++ b/templates/input-text.php
@@ -0,0 +1,17 @@
+<?php
+/**
+ * @var \WPDesk\Forms\Field $field
+ * @var \WPDesk\View\Renderer\Renderer $renderer
+ * @var string $name_prefix
+ * @var string $value
+ *
+ * @var string $template_name Real field template.
+ *
+ */
+?>
+<?php echo $renderer->render('input', [
+	'field' => $field,
+	'renderer' => $renderer,
+	'name_prefix' => $name_prefix,
+	'value' => $value,
+]); ?>
diff --git a/templates/input.php b/templates/input.php
new file mode 100644
index 0000000000000000000000000000000000000000..bad044ee6c41ea0163d952a6adc247ef981c016b
--- /dev/null
+++ b/templates/input.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * @var \WPDesk\Forms\Field $field
+ * @var string $name_prefix
+ * @var string $value
+ */
+?>
+
+<?php if ( ! in_array( $field->get_type(), [ 'text', 'hidden' ] ) ): ?>
+	<input type="hidden" name="<?php echo $name_prefix; ?>[<?php echo $field->get_name(); ?>]" value="no"/>
+<?php endif; ?>
+
+<?php if ( $field->get_type() === 'checkbox' && $field->has_sublabel() ) : ?>
+<label>
+<?php endif; ?>
+
+<input
+	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() ); ?>"
+
+	<?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 ): ?>
+<?php echo $key ?>="<?php echo 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(), [ 'text', 'hidden' ] ) ): ?>
+	value="<?php echo esc_html( $value ); ?>"
+<?php else: ?>
+	value="yes"
+	<?php if ( $value === 'yes' ): ?>checked="checked"<?php endif; ?>
+<?php endif; ?>
+/>
+
+<?php if ( $field->get_type() === 'checkbox' && $field->has_sublabel() ) : ?>
+	<?php echo esc_html( $field->get_sublabel() ); ?></label>
+<?php endif; ?>
diff --git a/templates/noonce.php b/templates/noonce.php
new file mode 100644
index 0000000000000000000000000000000000000000..7bf977476ab93f3ff998a53483cd924d612abaa1
--- /dev/null
+++ b/templates/noonce.php
@@ -0,0 +1,8 @@
+<?php
+/**
+ * @var \WPDesk\Forms\Field $field
+ * @var string $name_prefix
+ * @var string $value
+ */
+?>
+<?php wp_nonce_field( $field->get_meta_value( 'action' ), $name_prefix . $field->get_name() );
diff --git a/templates/paragraph.php b/templates/paragraph.php
new file mode 100644
index 0000000000000000000000000000000000000000..184e3943c5975b7f9d37ae199df61c3778c34a5f
--- /dev/null
+++ b/templates/paragraph.php
@@ -0,0 +1,15 @@
+<?php
+/**
+ * @var \WPDesk\Forms\Field $field
+ * @var string $name_prefix
+ * @var string $value
+ */
+?>
+
+<?php if ( $field->has_description() ): ?>
+	<tr>
+		<td style="padding-left:0;" colspan="2">
+			<p <?php if ( $field->has_classes() ): ?>class="<?php echo $field->get_classes(); ?>"<?php endif; ?>><?php echo wp_kses_post( $field->get_description() ); ?></p>
+		</td>
+	</tr>
+<?php endif; ?>
diff --git a/templates/product-select.php b/templates/product-select.php
new file mode 100644
index 0000000000000000000000000000000000000000..3efbbafe5f2a675bbd37171d63a1bdc2576e38ad
--- /dev/null
+++ b/templates/product-select.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * @var \WPDesk\Forms\Field $field
+ * @var string $name_prefix
+ * @var string[] $value
+ */
+?>
+
+<select class="wc-product-search" multiple="multiple" style="width: 50%;"
+        id="<?php echo esc_attr( $field->get_id() ); ?>"
+        name="<?php echo esc_attr( $name_prefix ); ?>[<?php echo esc_attr( $field->get_name() ); ?>][]"
+        data-placeholder="<?php esc_attr_e( 'Search for a product&hellip;', 'woocommerce' ); ?>"
+        data-action="woocommerce_json_search_products_and_variations">
+	<?php
+	foreach ( (array) $value as $product_id ) {
+		$product = wc_get_product( $product_id );
+		if ( is_object( $product ) ) {
+			echo '<option value="' . esc_attr( $product_id ) . '"' . selected( true, true,
+					false ) . '>' . wp_kses_post( $product->get_formatted_name() ) . '</option>';
+		}
+	}
+	?>
+</select>
+
+<script type="text/javascript">
+	jQuery(document.body).trigger('wc-enhanced-select-init');
+</script>
diff --git a/templates/select.php b/templates/select.php
new file mode 100644
index 0000000000000000000000000000000000000000..e2af9286f60c9ba1b455199e421b3349f6132316
--- /dev/null
+++ b/templates/select.php
@@ -0,0 +1,29 @@
+<?php
+/**
+ * @var \WPDesk\Forms\Field $field
+ * @var string $name_prefix
+ * @var string $value
+ */
+?>
+<select
+	id="<?php echo esc_attr( $field->get_id() ); ?>"
+	<?php if ($field->has_classes()): ?>class="<?php echo esc_attr( $field->get_classes() ); ?>"<?php endif; ?>
+	name="<?php echo esc_attr( $name_prefix ); ?>[<?php echo esc_attr( $field->get_name() ); ?>]"
+	<?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() ): ?><option value=""><?php echo esc_html( $field->get_placeholder() ); ?></option><?php endif; ?>
+
+	<?php foreach ( $field->get_possible_values() as $possible_value => $label ): ?>
+		<option
+			<?php if ( $possible_value === $value || (is_numeric($possible_value) && is_numeric($value) && (int) $possible_value === (int) $value )): ?>selected="selected"<?php endif; ?>
+			value="<?php echo esc_attr( $possible_value ); ?>"
+		><?php echo esc_html( $label ); ?></option>
+	<?php endforeach; ?>
+</select>
diff --git a/templates/textarea.php b/templates/textarea.php
new file mode 100644
index 0000000000000000000000000000000000000000..cf2f2a7006edeb1d2aa59d8b7c59a547b1a320e6
--- /dev/null
+++ b/templates/textarea.php
@@ -0,0 +1,23 @@
+<?php
+/**
+ * @var \WPDesk\Forms\Field $field
+ * @var string $name_prefix
+ * @var string $value
+ */
+?>
+
+<textarea
+    id="<?php echo esc_attr( $field->get_id() ); ?>"
+		<?php if ( $field->has_classes() ): ?>class="<?php echo esc_attr( $field->get_classes() ); ?>"<?php endif; ?>
+	name="<?php echo esc_attr( $name_prefix ); ?>[<?php echo esc_attr( $field->get_name() ); ?>]"
+    <?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() ): ?>placeholder="<?php echo esc_html( $field->get_placeholder() ); ?>"<?php endif; ?>
+><?php echo esc_html( $value ); ?></textarea>
diff --git a/templates/wyswig.php b/templates/wyswig.php
new file mode 100644
index 0000000000000000000000000000000000000000..fd1ea9daa103a72921b0fee100309329b59c21d8
--- /dev/null
+++ b/templates/wyswig.php
@@ -0,0 +1,26 @@
+<?php
+/**
+ * @var \WPDesk\Forms\Field $field
+ * @var string $name_prefix
+ * @var string $value
+ */
+?>
+<?php wp_print_styles( 'media-views' ); ?>
+<script>
+	window.SM_EditorInitialized = true;
+</script>
+
+
+<?php
+$id              = uniqid( 'wyswig_' );
+$editor_settings = array(
+	'textarea_name' => esc_attr( $name_prefix ) . '[' . esc_attr( $field->get_name() ) . ']'
+);
+
+wp_editor( wp_kses_post( $value ), $id, $editor_settings );
+?>
+<script type="text/javascript">
+	(function () {
+		ShopMagic.wyswig.init('<?php echo $id; ?>');
+	}());
+</script>