diff --git a/README.md b/README.md
index 378d78da0..9b2dd1d00 100644
--- a/README.md
+++ b/README.md
@@ -53,6 +53,7 @@ The accessibility column indicates whether the functionality is accessible in it
| [input-datepicker](./packages/input-datepicker) | [](https://www.npmjs.com/package/@lion/input-datepicker) | Input element for dates with a datepicker | ✔️ |
| [input-email](./packages/input-email) | [](https://www.npmjs.com/package/@lion/input-email) | Input element for e-mails | [#169][i169] |
| [input-iban](./packages/input-iban) | [](https://www.npmjs.com/package/@lion/input-iban) | Input element for IBANs | [#169][i169] |
+| [input-range](./packages/input-range) | [](https://www.npmjs.com/package/@lion/input-range) | Input element for a range of values | ✔️ |
| [radio](./packages/radio) | [](https://www.npmjs.com/package/@lion/radio) | Radio from element | ✔️ |
| [radio-group](./packages/radio-group) | [](https://www.npmjs.com/package/@lion/radio-group) | Group of radios | ✔️ |
| [select](./packages/select) | [](https://www.npmjs.com/package/@lion/select) | Simple native dropdown element | ✔️ |
diff --git a/packages/input-range/README.md b/packages/input-range/README.md
new file mode 100644
index 000000000..ce68b08eb
--- /dev/null
+++ b/packages/input-range/README.md
@@ -0,0 +1,32 @@
+# Input range
+
+[//]: # 'AUTO INSERT HEADER PREPUBLISH'
+
+`lion-input-range` component is based on the native range input.
+Its purpose is to provide a way for users to select one value from a range of values.
+
+## Features
+
+- Based on [lion-input](../input).
+- Shows `modelValue` and `unit` above the range input.
+- Shows `min` and `max` value after the range input.
+- Can hide the `min` and `max` value via `no-min-max-labels`.
+- Makes use of [formatNumber](../localize/docs/number.md) for formatting and parsing.
+
+## How to use
+
+### Installation
+
+```sh
+npm i --save @lion/input-range
+```
+
+```js
+import '@lion/input-range/lion-input-range.js';
+```
+
+### Example
+
+```html
+
+```
diff --git a/packages/input-range/index.js b/packages/input-range/index.js
new file mode 100644
index 000000000..e0d52d1e7
--- /dev/null
+++ b/packages/input-range/index.js
@@ -0,0 +1 @@
+export { LionInputRange } from './src/LionInputRange.js';
diff --git a/packages/input-range/lion-input-range.js b/packages/input-range/lion-input-range.js
new file mode 100644
index 000000000..f60d7d23d
--- /dev/null
+++ b/packages/input-range/lion-input-range.js
@@ -0,0 +1,3 @@
+import { LionInputRange } from './src/LionInputRange.js';
+
+customElements.define('lion-input-range', LionInputRange);
diff --git a/packages/input-range/package.json b/packages/input-range/package.json
new file mode 100644
index 000000000..4852eac80
--- /dev/null
+++ b/packages/input-range/package.json
@@ -0,0 +1,43 @@
+{
+ "name": "@lion/input-range",
+ "version": "0.0.0",
+ "description": "Provide a way for users to select one value from a range of values",
+ "author": "ing-bank",
+ "homepage": "https://github.com/ing-bank/lion/",
+ "license": "MIT",
+ "publishConfig": {
+ "access": "public"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/ing-bank/lion.git",
+ "directory": "packages/input-range"
+ },
+ "scripts": {
+ "prepublishOnly": "../../scripts/npm-prepublish.js"
+ },
+ "keywords": [
+ "lion",
+ "web-components",
+ "input-range",
+ "slider"
+ ],
+ "main": "index.js",
+ "module": "index.js",
+ "files": [
+ "src",
+ "stories",
+ "test",
+ "*.js"
+ ],
+ "dependencies": {
+ "@lion/core": "0.3.0",
+ "@lion/field": "0.6.8",
+ "@lion/input": "0.4.0",
+ "@lion/localize": "0.7.1"
+ },
+ "devDependencies": {
+ "@open-wc/demoing-storybook": "^0.2.0",
+ "@open-wc/testing": "^2.3.4"
+ }
+}
diff --git a/packages/input-range/src/LionInputRange.js b/packages/input-range/src/LionInputRange.js
new file mode 100644
index 000000000..b05ae558a
--- /dev/null
+++ b/packages/input-range/src/LionInputRange.js
@@ -0,0 +1,129 @@
+/* eslint-disable import/no-extraneous-dependencies */
+import { LocalizeMixin, formatNumber } from '@lion/localize';
+import { FieldCustomMixin } from '@lion/field';
+import { LionInput } from '@lion/input';
+import { html, css, unsafeCSS } from '@lion/core';
+
+/**
+ * LionInputRange: extension of lion-input
+ *
+ * @customElement `lion-input-range`
+ * @extends LionInput
+ */
+export class LionInputRange extends FieldCustomMixin(LocalizeMixin(LionInput)) {
+ static get properties() {
+ return {
+ min: Number,
+ max: Number,
+ unit: String,
+ step: {
+ type: Number,
+ reflect: true,
+ },
+ noMinMaxLabels: {
+ type: Boolean,
+ attribute: 'no-min-max-labels',
+ },
+ };
+ }
+
+ static rangeStyles(scope) {
+ return css`
+ /* Custom input range styling comes here, be aware that this won't work for polyfilled browsers */
+ .${scope} .form-control {
+ width: 100%;
+ box-shadow: none;
+ outline: none;
+ }
+ `;
+ }
+
+ connectedCallback() {
+ if (super.connectedCallback) super.connectedCallback();
+ this.type = 'range';
+ /* eslint-disable-next-line wc/no-self-class */
+ this.classList.add(this.scopedClass);
+
+ this.__setupStyleTag();
+ }
+
+ disconnectedCallback() {
+ if (super.disconnectedCallback) super.disconnectedCallback();
+ this.__teardownStyleTag();
+ }
+
+ constructor() {
+ super();
+ this.parser = modelValue => parseFloat(modelValue);
+ this.scopedClass = `${this.localName}-${Math.floor(Math.random() * 10000)}`;
+ }
+
+ updated(changedProperties) {
+ super.updated(changedProperties);
+
+ if (changedProperties.has('min')) {
+ this._inputNode.min = this.min;
+ }
+
+ if (changedProperties.has('max')) {
+ this._inputNode.max = this.max;
+ }
+
+ if (changedProperties.has('step')) {
+ this._inputNode.step = this.step;
+ }
+ }
+
+ firstUpdated(changedProperties) {
+ super.firstUpdated(changedProperties);
+ if (changedProperties.has('modelValue')) {
+ // TODO: find out why this hack is needed to display the initial modelValue
+ this.updateComplete.then(() => {
+ this._inputNode.value = this.modelValue;
+ });
+ }
+ }
+
+ inputGroupTemplate() {
+ return html`
+
+ ${formatNumber(this.formattedValue)}
+ ${this.unit}
+
+
+ `;
+ }
+
+ inputGroupInputTemplate() {
+ return html`
+
+ `;
+ }
+
+ __setupStyleTag() {
+ this.__styleTag = document.createElement('style');
+ this.__styleTag.innerHTML = this.constructor.rangeStyles(unsafeCSS(this.scopedClass));
+ this.insertBefore(this.__styleTag, this.childNodes[0]);
+ }
+
+ __teardownStyleTag() {
+ this.removeChild(this.__styleTag);
+ }
+}
diff --git a/packages/input-range/stories/index.stories.js b/packages/input-range/stories/index.stories.js
new file mode 100644
index 000000000..7f87dbedd
--- /dev/null
+++ b/packages/input-range/stories/index.stories.js
@@ -0,0 +1,111 @@
+import { storiesOf, html } from '@open-wc/demoing-storybook';
+import { css } from '@lion/core';
+
+import '../lion-input-range.js';
+
+const rangeDemoStyle = css`
+ .demo-range {
+ max-width: 400px;
+ }
+`;
+
+storiesOf('Forms | Input Range', module)
+ .add(
+ 'Default',
+ () => html`
+
+
+
+ `,
+ )
+ .add(
+ 'Help text',
+ () => html`
+
+
+
+ `,
+ )
+ .add(
+ 'Units',
+ () => html`
+
+
+
+ `,
+ )
+ .add(
+ 'With steps',
+ () => html`
+
+
+
+ `,
+ )
+ .add(
+ 'No min max labels',
+ () => html`
+
+
+ `,
+ )
+ .add(
+ 'Disabled',
+ () => html`
+
+
+
+ `,
+ );
diff --git a/packages/input-range/test/lion-input-range.test.js b/packages/input-range/test/lion-input-range.test.js
new file mode 100644
index 000000000..ccb3b2e9a
--- /dev/null
+++ b/packages/input-range/test/lion-input-range.test.js
@@ -0,0 +1,98 @@
+import { expect, fixture, nextFrame, html } from '@open-wc/testing';
+
+import '../lion-input-range.js';
+
+describe('', () => {
+ it('has a type = range', async () => {
+ const el = await fixture(``);
+ expect(el._inputNode.type).to.equal('range');
+ });
+
+ it('contain the scoped css class for the slotted input style', async () => {
+ const el = await fixture(`
+
+ `);
+ expect(el.classList.contains(el.scopedClass)).to.equal(true);
+ });
+
+ it('adds a style tag as the first child which contains a class selector to the element', async () => {
+ const el = await fixture(`
+
+ `);
+ expect(el.children[0].tagName).to.equal('STYLE');
+ expect(el.children[0].innerHTML).to.contain(el.scopedClass);
+ });
+
+ it('does cleanup of the style tag when moving or deleting the el', async () => {
+ const wrapper = await fixture(`
+
+ `);
+ const wrapper2 = await fixture(`
+
+ `);
+ const el = document.createElement('lion-input-range');
+ wrapper.appendChild(el);
+ wrapper2.appendChild(el);
+
+ expect(el.children[1].tagName).to.not.equal('STYLE');
+ });
+
+ it('displays the modelValue and unit', async () => {
+ const el = await fixture(html`
+
+ `);
+ expect(el.shadowRoot.querySelector('.input-range__value').innerText).to.equal('75');
+ expect(el.shadowRoot.querySelector('.input-range__unit').innerText).to.equal('%');
+ });
+
+ it('displays 2 tick labels (min and max values) by default', async () => {
+ const el = await fixture(``);
+ expect(el.shadowRoot.querySelectorAll('.input-range__limits span').length).to.equal(2);
+ expect(el.shadowRoot.querySelectorAll('.input-range__limits span')[0].innerText).to.equal(
+ el.min,
+ );
+ expect(el.shadowRoot.querySelectorAll('.input-range__limits span')[1].innerText).to.equal(
+ el.max,
+ );
+ });
+
+ it('update min and max attributes when min and max property change', async () => {
+ const el = await fixture(``);
+ el.min = '120';
+ el.max = '220';
+ await nextFrame();
+ expect(el._inputNode.min).to.equal(el.min);
+ expect(el._inputNode.max).to.equal(el.max);
+ });
+
+ it('can hide the tick labels', async () => {
+ const el = await fixture(
+ ``,
+ );
+ expect(el.shadowRoot.querySelectorAll('.input-group__input')[0]).dom.to.equal(`
+
+
+
+ `);
+ });
+
+ it('parser method should return a value parsed into a number format', async () => {
+ const el = await fixture(html`
+
+ `);
+ expect(el.modelValue).to.equal(150);
+ el._inputNode.value = '130';
+ el._inputNode.dispatchEvent(new Event('input'));
+ expect(el.modelValue).to.equal(130);
+ });
+
+ it('is accessible', async () => {
+ const el = await fixture(``);
+ await expect(el).to.be.accessible();
+ });
+
+ it('is accessible when disabled', async () => {
+ const el = await fixture(``);
+ await expect(el).to.be.accessible();
+ });
+});
diff --git a/stories/index.stories.js b/stories/index.stories.js
index 632b5a3f1..d19a466e2 100755
--- a/stories/index.stories.js
+++ b/stories/index.stories.js
@@ -8,6 +8,7 @@ import '../packages/input-date/stories/index.stories.js';
import '../packages/input-datepicker/stories/index.stories.js';
import '../packages/input-email/stories/index.stories.js';
import '../packages/input-iban/stories/index.stories.js';
+import '../packages/input-range/stories/index.stories.js';
import '../packages/option/stories/index.stories.js';
import '../packages/select/stories/index.stories.js';
import '../packages/fieldset/stories/index.stories.js';