diff --git a/ui-v2/app/components/sort-control.js b/ui-v2/app/components/sort-control.js
new file mode 100644
index 0000000000..f02e3602e9
--- /dev/null
+++ b/ui-v2/app/components/sort-control.js
@@ -0,0 +1,15 @@
+import Component from '@ember/component';
+import { get, set } from '@ember/object';
+export default Component.extend({
+ classNames: ['sort-control'],
+ direction: 'asc',
+ onchange: function() {},
+ actions: {
+ change: function(e) {
+ if (e.target.type === 'checkbox') {
+ set(this, 'direction', e.target.checked ? 'desc' : 'asc');
+ }
+ this.onchange({ target: { value: `${get(this, 'value')}:${get(this, 'direction')}` } });
+ },
+ },
+});
diff --git a/ui-v2/app/helpers/starts-with.js b/ui-v2/app/helpers/starts-with.js
new file mode 100644
index 0000000000..b4e25bc69e
--- /dev/null
+++ b/ui-v2/app/helpers/starts-with.js
@@ -0,0 +1,7 @@
+import { helper } from '@ember/component/helper';
+
+export function startsWith([needle, haystack = ''] /*, hash*/) {
+ return haystack.startsWith(needle);
+}
+
+export default helper(startsWith);
diff --git a/ui-v2/app/styles/base/components/sort-control/index.scss b/ui-v2/app/styles/base/components/sort-control/index.scss
new file mode 100644
index 0000000000..0650ebf9ba
--- /dev/null
+++ b/ui-v2/app/styles/base/components/sort-control/index.scss
@@ -0,0 +1 @@
+@import './skin';
diff --git a/ui-v2/app/styles/base/components/sort-control/skin.scss b/ui-v2/app/styles/base/components/sort-control/skin.scss
new file mode 100644
index 0000000000..4b9f1e6bff
--- /dev/null
+++ b/ui-v2/app/styles/base/components/sort-control/skin.scss
@@ -0,0 +1,17 @@
+%sort-control input {
+ display: none;
+}
+%sort-control label {
+ @extend %user-select-none;
+ cursor: pointer;
+}
+%sort-control input[type='checkbox'] + label::after {
+ @extend %as-pseudo;
+ opacity: 0.7;
+}
+%sort-control input[type='checkbox'] + label::after {
+ @extend %with-arrow-down-icon;
+}
+%sort-control input[type='checkbox']:checked + label::after {
+ @extend %with-arrow-up-icon;
+}
diff --git a/ui-v2/app/styles/components/index.scss b/ui-v2/app/styles/components/index.scss
index b446c927aa..e0ee99db2b 100644
--- a/ui-v2/app/styles/components/index.scss
+++ b/ui-v2/app/styles/components/index.scss
@@ -31,3 +31,4 @@
@import './modal-dialog';
@import './notice';
@import './tooltip';
+@import './sort-control';
diff --git a/ui-v2/app/styles/components/sort-control.scss b/ui-v2/app/styles/components/sort-control.scss
new file mode 100644
index 0000000000..aa08af693b
--- /dev/null
+++ b/ui-v2/app/styles/components/sort-control.scss
@@ -0,0 +1,4 @@
+@import '../base/components/sort-control/index';
+.sort-control {
+ @extend %sort-control;
+}
diff --git a/ui-v2/app/templates/components/sort-control.hbs b/ui-v2/app/templates/components/sort-control.hbs
new file mode 100644
index 0000000000..31a9556362
--- /dev/null
+++ b/ui-v2/app/templates/components/sort-control.hbs
@@ -0,0 +1,7 @@
+
+{{#if checked}}
+
+{{/if}}
+
\ No newline at end of file
diff --git a/ui-v2/tests/integration/components/sort-control-test.js b/ui-v2/tests/integration/components/sort-control-test.js
new file mode 100644
index 0000000000..0cf4192c4a
--- /dev/null
+++ b/ui-v2/tests/integration/components/sort-control-test.js
@@ -0,0 +1,50 @@
+import { moduleForComponent, test } from 'ember-qunit';
+import hbs from 'htmlbars-inline-precompile';
+
+moduleForComponent('sort-control', 'Integration | Component | sort control', {
+ integration: true,
+});
+
+test('it renders', function(assert) {
+ // Set any properties with this.set('myProperty', 'value');
+ // Handle any actions with this.on('myAction', function(val) { ... });
+
+ this.render(hbs`{{sort-control}}`);
+
+ assert.equal(
+ this.$()
+ .text()
+ .trim(),
+ ''
+ );
+
+ // Template block usage:
+ this.render(hbs`
+ {{#sort-control}}
+ template block text
+ {{/sort-control}}
+ `);
+
+ assert.equal(
+ this.$()
+ .text()
+ .trim(),
+ 'template block text'
+ );
+});
+test('it changes direction and calls onchange when clicked/activated', function(assert) {
+ assert.expect(2);
+ let count = 0;
+ this.on('change', e => {
+ if (count === 0) {
+ assert.equal(e.target.value, 'sort:desc');
+ } else {
+ assert.equal(e.target.value, 'sort:asc');
+ }
+ count++;
+ });
+ this.render(hbs`{{sort-control checked=true value='sort' onchange=(action 'change')}}`);
+ const $label = this.$('label');
+ $label.trigger('click');
+ $label.trigger('click');
+});
diff --git a/ui-v2/tests/integration/helpers/starts-with-test.js b/ui-v2/tests/integration/helpers/starts-with-test.js
new file mode 100644
index 0000000000..825a631c3e
--- /dev/null
+++ b/ui-v2/tests/integration/helpers/starts-with-test.js
@@ -0,0 +1,20 @@
+import { moduleForComponent, test } from 'ember-qunit';
+import hbs from 'htmlbars-inline-precompile';
+
+moduleForComponent('starts-with', 'helper:starts-with', {
+ integration: true,
+});
+
+// Replace this with your real tests.
+test('it renders', function(assert) {
+ this.set('inputValue', '1234');
+
+ this.render(hbs`{{starts-with inputValue}}`);
+
+ assert.equal(
+ this.$()
+ .text()
+ .trim(),
+ 'false'
+ );
+});