diff --git a/js/src/ui/SectionList/index.js b/js/src/ui/SectionList/index.js
new file mode 100644
index 000000000..25971968d
--- /dev/null
+++ b/js/src/ui/SectionList/index.js
@@ -0,0 +1,17 @@
+// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
+// This file is part of Parity.
+
+// Parity is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity. If not, see .
+
+export default from './sectionList';
diff --git a/js/src/ui/SectionList/sectionList.css b/js/src/ui/SectionList/sectionList.css
new file mode 100644
index 000000000..2310a88ac
--- /dev/null
+++ b/js/src/ui/SectionList/sectionList.css
@@ -0,0 +1,72 @@
+/* Copyright 2015, 2016 Parity Technologies (UK) Ltd.
+/* This file is part of Parity.
+/*
+/* Parity is free software: you can redistribute it and/or modify
+/* it under the terms of the GNU General Public License as published by
+/* the Free Software Foundation, either version 3 of the License, or
+/* (at your option) any later version.
+/*
+/* Parity is distributed in the hope that it will be useful,
+/* but WITHOUT ANY WARRANTY; without even the implied warranty of
+/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+/* GNU General Public License for more details.
+/*
+/* You should have received a copy of the GNU General Public License
+/* along with Parity. If not, see .
+*/
+
+.section {
+ margin-bottom: 1em;
+ position: relative;
+
+ .overlay {
+ background: rgba(0, 0, 0, 0.85);
+ bottom: 0;
+ left: 0;
+ padding: 1.5em;
+ position: absolute;
+ right: 0;
+ top: 0;
+ z-index: 199;
+ }
+
+ .row {
+ display: flex;
+ justify-content: center;
+
+ /* TODO: As per JS comments, the flex-base could be adjusted in the future to allow for */
+ /* case where <> 3 columns are required should the need arrise from a UI pov. */
+ .item {
+ box-sizing: border-box;
+ cursor: pointer;
+ flex: 0 1 33.33%;
+ height: 100%;
+ opacity: 0.75;
+ padding: 0.25em;
+ transition: all 0.75s cubic-bezier(0.23, 1, 0.32, 1);
+
+ /* TODO: The hover and no-hover states can be improved to not "just appear" */
+ &:not(:hover) {
+ & [data-hover="hide"] {
+ }
+
+ & [data-hover="show"] {
+ display: none;
+ }
+ }
+
+ &:hover {
+ flex: 0 0 50%;
+ opacity: 1;
+ z-index: 100;
+
+ & [data-hover="hide"] {
+ display: none;
+ }
+
+ & [data-hover="show"] {
+ }
+ }
+ }
+ }
+}
diff --git a/js/src/ui/SectionList/sectionList.js b/js/src/ui/SectionList/sectionList.js
new file mode 100644
index 000000000..82e39763b
--- /dev/null
+++ b/js/src/ui/SectionList/sectionList.js
@@ -0,0 +1,95 @@
+// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
+// This file is part of Parity.
+
+// Parity is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity. If not, see .
+
+import React, { Component, PropTypes } from 'react';
+
+import { chunkArray } from '~/util/array';
+import { arrayOrObjectProptype, nodeOrStringProptype } from '~/util/proptypes';
+
+import styles from './sectionList.css';
+
+// TODO: We probably want this to be passed via props - additional work required in that case to
+// support the styling for both the hover and no-hover CSS for the pre/post sizes. Future work only
+// if/when required.
+const ITEMS_PER_ROW = 3;
+
+export default class SectionList extends Component {
+ static propTypes = {
+ className: PropTypes.string,
+ items: arrayOrObjectProptype().isRequired,
+ renderItem: PropTypes.func.isRequired,
+ overlay: nodeOrStringProptype()
+ }
+
+ render () {
+ const { className, items } = this.props;
+
+ if (!items || !items.length) {
+ return null;
+ }
+
+ return (
+
+ { this.renderOverlay() }
+ { chunkArray(items, ITEMS_PER_ROW).map(this.renderRow) }
+
+ );
+ }
+
+ renderOverlay () {
+ const { overlay } = this.props;
+
+ if (!overlay) {
+ return null;
+ }
+
+ return (
+
+ { overlay }
+
+ );
+ }
+
+ renderRow = (row, index) => {
+ return (
+
+ { row.map(this.renderItem) }
+
+ );
+ }
+
+ renderItem = (item, index) => {
+ const { renderItem } = this.props;
+
+ // NOTE: Any children that is to be showed or hidden (depending on hover state)
+ // should have the data-hover="show|hide" attributes. For the current implementation
+ // this does the trick, however there may be a case for adding a hover attribute
+ // to an item (mouseEnter/mouseLeave events) and then adjusting the styling with
+ // :root[hover]/:root:not[hover] for the tragetted elements. Currently it is a
+ // CSS-only solution to let the browser do all the work via selectors.
+ return (
+
+ { renderItem(item, index) }
+
+ );
+ }
+}
diff --git a/js/src/ui/SectionList/sectionList.spec.js b/js/src/ui/SectionList/sectionList.spec.js
new file mode 100644
index 000000000..480b79a52
--- /dev/null
+++ b/js/src/ui/SectionList/sectionList.spec.js
@@ -0,0 +1,103 @@
+// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
+// This file is part of Parity.
+
+// Parity is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity. If not, see .
+
+import { shallow } from 'enzyme';
+import React from 'react';
+import sinon from 'sinon';
+
+import SectionList from './';
+
+const ITEMS = ['itemA', 'itemB', 'itemC', 'itemD', 'itemE'];
+
+let component;
+let instance;
+let renderItem;
+
+function render (props = {}) {
+ renderItem = sinon.stub();
+ component = shallow(
+
+ );
+ instance = component.instance();
+
+ return component;
+}
+
+describe('SectionList', () => {
+ beforeEach(() => {
+ render();
+ });
+
+ it('renders defaults', () => {
+ expect(component).to.be.ok;
+ });
+
+ it('adds className as specified', () => {
+ expect(component.hasClass('testClass')).to.be.true;
+ });
+
+ describe('instance methods', () => {
+ describe('renderRow', () => {
+ let row;
+
+ beforeEach(() => {
+ sinon.stub(instance, 'renderItem');
+ row = instance.renderRow(['testA', 'testB']);
+ });
+
+ afterEach(() => {
+ instance.renderItem.restore();
+ });
+
+ it('renders a row', () => {
+ expect(row).to.be.ok;
+ });
+
+ it('adds a key for the row', () => {
+ expect(row.key).to.be.ok;
+ });
+
+ it('calls renderItem for the items', () => {
+ expect(instance.renderItem).to.have.been.calledTwice;
+ });
+ });
+
+ describe('renderItem', () => {
+ let item;
+
+ beforeEach(() => {
+ item = instance.renderItem('testItem', 50);
+ });
+
+ it('renders an item', () => {
+ expect(item).to.be.ok;
+ });
+
+ it('adds a key for the item', () => {
+ expect(item.key).to.be.ok;
+ });
+
+ it('calls the external renderer', () => {
+ expect(renderItem).to.have.been.calledWith('testItem', 50);
+ });
+ });
+ });
+});
diff --git a/js/src/ui/index.js b/js/src/ui/index.js
index d66663d8e..637a16daf 100644
--- a/js/src/ui/index.js
+++ b/js/src/ui/index.js
@@ -47,6 +47,7 @@ import Page from './Page';
import ParityBackground from './ParityBackground';
import PasswordStrength from './Form/PasswordStrength';
import QrCode from './QrCode';
+import SectionList from './SectionList';
import ShortenedHash from './ShortenedHash';
import SignerIcon from './SignerIcon';
import Tags from './Tags';
@@ -102,8 +103,9 @@ export {
PasswordStrength,
QrCode,
RadioButtons,
- ShortenedHash,
Select,
+ ShortenedHash,
+ SectionList,
SignerIcon,
Tags,
Tooltip,
diff --git a/js/src/util/array.js b/js/src/util/array.js
new file mode 100644
index 000000000..e433c0163
--- /dev/null
+++ b/js/src/util/array.js
@@ -0,0 +1,26 @@
+// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
+// This file is part of Parity.
+
+// Parity is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity. If not, see .
+
+// http://stackoverflow.com/questions/11318680/split-array-into-chunks-of-n-length
+export function chunkArray (array, size) {
+ return array
+ .map((item, index) => {
+ return index % size === 0
+ ? array.slice(index, index + size)
+ : null;
+ })
+ .filter((item) => item);
+}
diff --git a/js/src/util/array.spec.js b/js/src/util/array.spec.js
new file mode 100644
index 000000000..7d760388d
--- /dev/null
+++ b/js/src/util/array.spec.js
@@ -0,0 +1,29 @@
+// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
+// This file is part of Parity.
+
+// Parity is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity. If not, see .
+
+import { chunkArray } from './array';
+
+describe('util/array', () => {
+ describe('chunkArray', () => {
+ it('splits array into equal chunks', () => {
+ expect(chunkArray([1, 2, 3, 4], 2)).to.deep.equal([[1, 2], [3, 4]]);
+ });
+
+ it('splits array into equal chunks (non-divisible)', () => {
+ expect(chunkArray([1, 2, 3, 4], 3)).to.deep.equal([[1, 2, 3], [4]]);
+ });
+ });
+});