diff --git a/currency.css b/currency.css
new file mode 100644
index 0000000..451cfdb
--- /dev/null
+++ b/currency.css
@@ -0,0 +1,3 @@
+.form-type-currency-amount .form-item {
+  display: inline;
+}
\ No newline at end of file
diff --git a/currency.info b/currency.info
index 7d83714..13c808c 100755
--- a/currency.info
+++ b/currency.info
@@ -10,6 +10,7 @@ files[] = includes/CurrencyAmountViewsHandlerField.inc
 files[] = includes/CurrencyExportableInterface.inc
 files[] = includes/CurrencyLocalePattern.inc
 files[] = includes/CurrencyViewsHandlerField.inc
+files[] = tests/CurrencyAmountFormElementWebTestCase.test
 files[] = tests/CurrencyAmountViewsHandlerFieldWebTestCase.test
 files[] = tests/CurrencyCRUDWebTestCase.test
 files[] = tests/CurrencyLocalePatternCRUDWebTestCase.test
diff --git a/currency.module b/currency.module
index 1475ed8..126c305 100644
--- a/currency.module
+++ b/currency.module
@@ -5,6 +5,10 @@
  * Provides currency information and allows users to add custom currencies.
  */
 
+use BartFeenstra\Currency\AmountInvalidDecimalSeparatorException;
+use BartFeenstra\Currency\AmountNotNumericException;
+use BartFeenstra\Currency\Input;
+
 require_once __DIR__ . '/vendor/autoload.php';
 
 /**
@@ -33,6 +37,27 @@ function currency_hook_info() {
 }
 
 /**
+ * Implements hook_element_info().
+ */
+function currency_element_info() {
+  // An element to collect an amount of money and convert it to a float.
+  $elements['currency_amount'] = array(
+    '#input' => TRUE,
+    '#process' => array('currency_form_currency_amount_process'),
+    '#element_validate' => array('currency_form_currency_amount_validate'),
+    // The minimum amount that needs to be entered. Use FALSE to omit.
+    '#minimum_amount' => FALSE,
+    // The maximum amount that needs to be entered. Use FALSE to omit.
+    '#maximum_amount' => FALSE,
+    // The ISO 4217 code of the currency the amount should be in. Use FALSE to
+    // let users choose.
+    '#currency_code' => FALSE,
+  );
+
+  return $elements;
+}
+
+/**
  * Implements hook_views_api().
  */
 function currency_views_api() {
@@ -64,3 +89,130 @@ function currency_ctools_export_object_save(CurrencyExportableInterface $exporta
     ->fields($fields)
     ->execute();
 }
+
+/**
+ * Implements form process callback for a currency_amount element.
+ */
+function currency_form_currency_amount_process(array $element, array &$form_state, array &$form) {
+  // Validate element configuration.
+  if ($element['#minimum_amount'] !== FALSE && !is_numeric($element['#minimum_amount'])) {
+    throw new Exception(t('The minimum amount must be a decimal number.'));
+  }
+  if ($element['#maximum_amount'] !== FALSE && !is_numeric($element['#maximum_amount'])) {
+    throw new Exception(t('The maximum amount must be a decimal number.'));
+  }
+  ctools_include('export');
+  $currency = FALSE;
+  if ($element['#currency_code'] !== FALSE) {
+    $currency = ctools_export_crud_load('currency', $element['#currency_code']);
+    if (!$currency) {
+      throw new Exception(t('A valid ISO 4217 currency code needs to be set.'));
+    }
+  }
+  else {
+    $currency = ctools_export_crud_load('currency', 'XXX');
+  }
+
+  // Modify the element.
+  $element['#tree'] = TRUE;
+  $element['#theme_wrappers'][] = 'form_element';
+  $element['#attached']['css'] = array(
+    drupal_get_path('module', 'currency') . '/currency.css',
+  );
+
+  // Add the currency element.
+  if ($element['#currency_code']) {
+    $element['currency_code'] = array(
+      '#type' => 'value',
+      '#value' => $currency->ISO4217Code,
+    );
+    $element['currency_code_title'] = array(
+      '#type' => 'item',
+      '#title' => t('Currency'),
+      '#title_display' => 'invisible',
+      '#markup' => $currency->title,
+    );
+  }
+  else {
+    $element['currency_code'] = array(
+      '#type' => 'select',
+      '#title' => t('Currency'),
+      '#title_display' => 'invisible',
+      '#options' => currency_options(),
+      '#required' => $element['#required'],
+      '#default_value' => 'XXX',
+    );
+  }
+
+  // Add the amount element.
+  $description = NULL;
+  if ($element['#minimum_amount'] !== FALSE) {
+    $description = t('The minimum amount is !amount.', array(
+      '!amount' => $currency->format($element['#minimum_amount']),
+    ));
+  }
+  $element['amount'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Amount'),
+    '#title_display' => 'invisible',
+    '#description' => $description,
+    '#required' => $element['#required'],
+    '#size' => 9,
+  );
+
+  return $element;
+}
+
+/**
+ * Implements form validate callback for a currency_amount element.
+ */
+function currency_form_currency_amount_validate(array $element, array &$form_state) {
+  $amount = $element['#value']['amount'];
+  $currency_code = $element['#value']['currency_code'];
+
+  try {
+    $amount = Input::parseAmount($amount);
+  }
+  catch (AmountInvalidDecimalSeparatorException $e) {
+    form_error($element['amount'], t('The amount can only have no or one decimal separator and it must be one of %decimal_separators.', array(
+     '%decimal_separators' => implode(Input::$decimalSeparators),
+    )));
+  }
+  catch (AmountNotNumericException $e) {
+    form_error($element['amount'], t('The amount can only consist of a minus sign, decimals and one decimal mark.'));
+  }
+
+  // Confirm the amount lies within the allowed range.
+  $currency = ctools_export_crud_load('currency', $currency_code);
+  if ($element['#minimum_amount'] !== FALSE && $amount < $element['#minimum_amount']) {
+    form_error($element['amount'], t('The minimum amount is !amount.', array(
+      '!amount' => $currency->format($element['#minimum_amount']),
+    )));
+  }
+  elseif ($element['#maximum_amount'] !== FALSE && $amount > $element['#maximum_amount']) {
+    form_error($element['amount'], t('The maximum amount is !amount.', array(
+      '!amount' => $currency->format($element['#maximum_amount']),
+    )));
+  }
+}
+
+/**
+ * Returns an options list of currencies.
+ *
+ * @return array
+ *  Keys are ISO 4217 codes and values are currency titles.
+ */
+function currency_options() {
+  $options = array();
+  foreach (ctools_export_crud_load_all('currency') as $currency) {
+    if ($currency->title) {
+      $options[$currency->ISO4217Code] = t('@currency_title (@currency_code)', array(
+        '@currency_title' => $currency->title,
+        '@currency_code' => $currency->ISO4217Code,
+      ));
+    }
+  }
+  asort($options);
+
+  return $options;
+}
diff --git a/currency_test/currency_test.info b/currency_test/currency_test.info
new file mode 100644
index 0000000..79f168b
--- /dev/null
+++ b/currency_test/currency_test.info
@@ -0,0 +1,5 @@
+name = Currency tests
+description = "Supports Currency's automated tests."
+core = 7.x
+dependencies[] = currency
+hidden = TRUE
diff --git a/currency_test/currency_test.module b/currency_test/currency_test.module
new file mode 100644
index 0000000..f5e43cf
--- /dev/null
+++ b/currency_test/currency_test.module
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * @file
+ * Hook implementations and shared functions.
+ *
+ * @see CurrenctCurrencyAmountFormElementWebTestCase
+ */
+
+/**
+ * Implements hook_menu().
+ */
+function currency_test_menu() {
+  // The second URL path argument is an optional ISO 4217 currency code, and
+  // the third argument is an optional integer value specifying a minimum
+  // amount.
+  $items['currency_test-form-element-currency-amount/%/%'] = array(
+    'title' => 'Test the currency_amount form element',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('currency_test_form_currency_amount'),
+    'access callback' => TRUE,
+  );
+
+  return $items;
+}
+
+/**
+ * Implements form build callback: show a currency_amount element.
+ */
+function currency_test_form_currency_amount(array $form, array &$form_state) {
+  $form['#submit'] = array('currency_test_form_submit');
+  $form['amount'] = array(
+    '#type' => 'currency_amount',
+    '#title' => t('Foo amount'),
+    '#minimum_amount' => arg(1),
+    '#maximum_amount' => arg(2),
+  );
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Submit'),
+  );
+
+  return $form;
+}
+
+/**
+ * Implements form submit callback for testing the submission of form elements.
+ */
+function currency_test_form_submit(array $form, array &$form_state) {
+  // Redirect, so we can test whether form errors were thrown.
+  $form_state['redirect'] = 'user';
+}
diff --git a/tests/CurrencyAmountFormElementWebTestCase.test b/tests/CurrencyAmountFormElementWebTestCase.test
new file mode 100644
index 0000000..6d05623
--- /dev/null
+++ b/tests/CurrencyAmountFormElementWebTestCase.test
@@ -0,0 +1,65 @@
+<?php
+
+/**
+ * Contains class CurrencyAmountFormElementWebTestCase.
+ */
+
+/**
+ * Tests the currency_amount form element.
+ */
+class CurrencyAmountFormElementWebTestCase extends DrupalWebTestCase {
+
+  static function getInfo() {
+    return array(
+      'name' => 'Currency amount form element',
+      'group' => 'Currency',
+    );
+  }
+
+  function setUp(array $modules = array()) {
+    parent::setUp($modules + array('currency_test'));
+  }
+
+  /**
+   * Test validation.
+   */
+  function testValidation() {
+    $path = 'currency_test-form-element-currency-amount/50.00/100';
+
+    // Test valid values.
+    $values =  array(
+      'amount[amount]' => '50,95',
+      'amount[currency_code]' => 'EUR',
+    );
+    $this->drupalPost($path, $values, t('Submit'));
+    $this->assertUrl('user', array(), 'A valid value higher than an explicitely configured <em>#minimum_value</em> triggers form submission.');
+
+    // Test illegal characters.
+    $values =  array(
+      'amount[amount]' => $this->randomName(2),
+    );
+    $this->drupalPost($path, $values, t('Submit'));
+    $this->assertText(t('The amount can only consist of a minus sign, decimals and one decimal mark.'), 'Non-numeric characters that are no period, comma, or minus sign throw a validation error.');
+
+    // Test multiple decimal marks.
+    $values =  array(
+      'amount[amount]' => '49,.95',
+    );
+    $this->drupalPost($path, $values, t('Submit'));
+    $this->assertText(t('The amount can only have no or one decimal separator and it must be one of '), 'Multiple decimal marks throw a validation error.');
+
+    // Test the minimum value.
+    $values =  array(
+      'amount[amount]' => '49.95',
+    );
+    $this->drupalPost($path, $values, t('Submit'));
+    $this->assertText(t('The minimum amount is '), 'An amount below the minimum throws a validation error.');
+
+    // Test the maximum value.
+    $values =  array(
+      'amount[amount]' => '49.95',
+    );
+    $this->drupalPost($path, $values, t('Submit'));
+    $this->assertText(t('The minimum amount is '), 'An amount above the maximum throws a validation error.');
+  }
+}
