diff --git a/src/component-index.js b/src/component-index.js
index e250838cc4..19a016aec8 100644
--- a/src/component-index.js
+++ b/src/component-index.js
@@ -107,6 +107,8 @@ module.exports.components['views.rooms.UserTile'] = require('./components/views/
 module.exports.components['views.settings.ChangeAvatar'] = require('./components/views/settings/ChangeAvatar');
 module.exports.components['views.settings.ChangeDisplayName'] = require('./components/views/settings/ChangeDisplayName');
 module.exports.components['views.settings.ChangePassword'] = require('./components/views/settings/ChangePassword');
+module.exports.components['views.settings.DevicesPanel'] = require('./components/views/settings/DevicesPanel');
+module.exports.components['views.settings.DevicesPanelEntry'] = require('./components/views/settings/DevicesPanelEntry');
 module.exports.components['views.settings.EnableNotificationsButton'] = require('./components/views/settings/EnableNotificationsButton');
 module.exports.components['views.voip.CallView'] = require('./components/views/voip/CallView');
 module.exports.components['views.voip.IncomingCallBox'] = require('./components/views/voip/IncomingCallBox');
diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js
index 75fe1f0825..6555668ff4 100644
--- a/src/components/structures/UserSettings.js
+++ b/src/components/structures/UserSettings.js
@@ -319,7 +319,7 @@ module.exports = React.createClass({
         );
     },
 
-    _renderDeviceInfo: function() {
+    _renderCryptoInfo: function() {
         if (!UserSettingsStore.isFeatureEnabled("e2e_encryption")) {
             return null;
         }
@@ -340,6 +340,45 @@ module.exports = React.createClass({
         );
     },
 
+    _renderDevicesPanel: function() {
+        if (!UserSettingsStore.isFeatureEnabled("e2e_encryption")) {
+            return null;
+        }
+        var DevicesPanel = sdk.getComponent('settings.DevicesPanel');
+        return (
+            <div>
+                <h3>Devices</h3>
+                <DevicesPanel className="mx_UserSettings_section" />
+            </div>
+        );
+    },
+
+    _renderLabs: function () {
+        let features = LABS_FEATURES.map(feature => (
+            <div key={feature.id} className="mx_UserSettings_toggle">
+                <input
+                    type="checkbox"
+                    id={feature.id}
+                    name={feature.id}
+                    defaultChecked={UserSettingsStore.isFeatureEnabled(feature.id)}
+                    onChange={e => {
+                        UserSettingsStore.setFeatureEnabled(feature.id, e.target.checked);
+                        this.forceUpdate();
+                    }}/>
+                <label htmlFor={feature.id}>{feature.name}</label>
+            </div>
+        ));
+        return (
+            <div>
+                <h3>Labs</h3>
+                <div className="mx_UserSettings_section">
+                    <p>These are experimental features that may break in unexpected ways. Use with caution.</p>
+                    {features}
+                </div>
+            </div>
+        )
+    },
+
     render: function() {
         var self = this;
         var Loader = sdk.getComponent("elements.Spinner");
@@ -360,6 +399,7 @@ module.exports = React.createClass({
         var ChangeAvatar = sdk.getComponent('settings.ChangeAvatar');
         var Notifications = sdk.getComponent("settings.Notifications");
         var EditableText = sdk.getComponent('elements.EditableText');
+
         var avatarUrl = (
             this.state.avatarUrl ? MatrixClientPeg.get().mxcUrlToHttp(this.state.avatarUrl) : null
         );
@@ -434,30 +474,6 @@ module.exports = React.createClass({
             </div>);
         }
 
-        this._renderLabs = function () {
-            let features = LABS_FEATURES.map(feature => (
-                <div key={feature.id} className="mx_UserSettings_toggle">
-                    <input
-                           type="checkbox"
-                           id={feature.id}
-                           name={feature.id}
-                           defaultChecked={UserSettingsStore.isFeatureEnabled(feature.id)}
-                           onChange={e => UserSettingsStore.setFeatureEnabled(feature.id, e.target.checked)} />
-                    <label htmlFor={feature.id}>{feature.name}</label>
-                </div>
-            ));
-            return (
-                <div>
-                    <h3>Labs</h3>
-
-                    <div className="mx_UserSettings_section">
-                        <p>These are experimental features that may break in unexpected ways. Use with caution.</p>
-                        {features}
-                    </div>
-                </div>
-            )
-        };
-
         return (
             <div className="mx_UserSettings">
                 <SimpleRoomHeader title="Settings" onCancelClick={ this.props.onClose }/>
@@ -510,10 +526,9 @@ module.exports = React.createClass({
                 {notification_area}
 
                 {this._renderUserInterfaceSettings()}
-
-                {this._renderDeviceInfo()}
-
                 {this._renderLabs()}
+                {this._renderDevicesPanel()}
+                {this._renderCryptoInfo()}
 
                 <h3>Advanced</h3>
 
diff --git a/src/components/views/settings/DevicesPanel.js b/src/components/views/settings/DevicesPanel.js
new file mode 100644
index 0000000000..8dd6bb9230
--- /dev/null
+++ b/src/components/views/settings/DevicesPanel.js
@@ -0,0 +1,138 @@
+/*
+Copyright 2016 OpenMarket Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from 'react';
+import classNames from 'classnames';
+
+import sdk from '../../../index';
+import MatrixClientPeg from '../../../MatrixClientPeg';
+
+
+export default class DevicesPanel extends React.Component {
+    constructor(props, context) {
+        super(props, context);
+
+        this.state = {
+            devices: undefined,
+            deviceLoadError: undefined,
+        };
+
+        this._unmounted = false;
+
+        this._renderDevice = this._renderDevice.bind(this);
+    }
+
+    componentDidMount() {
+        this._loadDevices();
+    }
+
+    componentWillUnmount() {
+        this._unmounted = true;
+    }
+
+    _loadDevices() {
+        MatrixClientPeg.get().getDevices().done(
+            (resp) => {
+                if (this._unmounted) { return; }
+                this.setState({devices: resp.devices || []});
+            },
+            (error) => {
+                if (this._unmounted) { return; }
+                var errtxt;
+                if (err.httpStatus == 404) {
+                    // 404 probably means the HS doesn't yet support the API.
+                    errtxt = "Your home server does not support device management.";
+                } else {
+                    console.error("Error loading devices:", error);
+                    errtxt = "Unable to load device list.";
+                }
+                this.setState({deviceLoadError: errtxt});
+            }
+        );
+    }
+
+
+    /**
+     * compare two devices, sorting from most-recently-seen to least-recently-seen
+     * (and then, for stability, by device id)
+     */
+    _deviceCompare(a, b) {
+        // return < 0 if a comes before b, > 0 if a comes after b.
+        const lastSeenDelta =
+              (b.last_seen_ts || 0) - (a.last_seen_ts || 0);
+
+        if (lastSeenDelta !== 0) { return lastSeenDelta; }
+
+        const idA = a.device_id;
+        const idB = b.device_id;
+        return (idA < idB) ? -1 : (idA > idB) ? 1 : 0;
+    }
+
+    _onDeviceDeleted(device) {
+        if (this._unmounted) { return; }
+
+        // delete the removed device from our list.
+        const removed_id = device.device_id;
+        this.setState((state, props) => {
+            const newDevices = state.devices.filter(
+                d => { return d.device_id != removed_id }
+            );
+            return { devices: newDevices };
+        });
+    }
+
+    _renderDevice(device) {
+        var DevicesPanelEntry = sdk.getComponent('settings.DevicesPanelEntry');
+        return (
+            <DevicesPanelEntry key={device.device_id} device={device}
+               onDeleted={()=>{this._onDeviceDeleted(device)}} />
+        );
+    }
+
+    render() {
+        const Spinner = sdk.getComponent("elements.Spinner");
+
+        if (this.state.deviceLoadError !== undefined) {
+            const classes = classNames(this.props.className, "error");
+            return (
+                <div className={classes}>
+                    {this.state.deviceLoadError}
+                </div>
+            );
+        }
+
+        const devices = this.state.devices;
+        if (devices === undefined) {
+            // still loading
+            const classes = this.props.className;
+            return <Spinner className={classes}/>;
+        }
+
+        devices.sort(this._deviceCompare);
+
+        const classes = classNames(this.props.className, "mx_DevicesPanel");
+        return (
+            <div className={classes}>
+                <div className="mx_DevicesPanel_header">
+                    <div className="mx_DevicesPanel_deviceName">Name</div>
+                    <div className="mx_DevicesPanel_deviceLastSeen">Last seen</div>
+                    <div className="mx_DevicesPanel_deviceButtons"></div>
+                </div>
+                {devices.map(this._renderDevice)}
+            </div>
+        );
+    }
+}
diff --git a/src/components/views/settings/DevicesPanelEntry.js b/src/components/views/settings/DevicesPanelEntry.js
new file mode 100644
index 0000000000..6858e62102
--- /dev/null
+++ b/src/components/views/settings/DevicesPanelEntry.js
@@ -0,0 +1,134 @@
+/*
+Copyright 2016 OpenMarket Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from 'react';
+import classNames from 'classnames';
+import q from 'q';
+
+import sdk from '../../../index';
+import MatrixClientPeg from '../../../MatrixClientPeg';
+import DateUtils from '../../../DateUtils';
+
+export default class DevicesPanelEntry extends React.Component {
+    constructor(props, context) {
+        super(props, context);
+
+        this.state = {
+            deleting: false,
+            deleteError: undefined,
+        };
+
+        this._unmounted = false;
+
+        this._onDeleteClick = this._onDeleteClick.bind(this);
+        this._onDisplayNameChanged = this._onDisplayNameChanged.bind(this);
+    }
+
+    componentWillUnmount() {
+        this._unmounted = true;
+    }
+
+    _onDisplayNameChanged(value) {
+        const device = this.props.device;
+        return MatrixClientPeg.get().setDeviceDetails(device.device_id, {
+            display_name: value,
+        }).catch((e) => {
+            console.error("Error setting device display name", e);
+            throw new Error("Failed to set display name");
+        });
+    }
+
+    _onDeleteClick() {
+        const device = this.props.device;
+        this.setState({deleting: true});
+
+        MatrixClientPeg.get().deleteDevice(device.device_id).done(
+            () => {
+                this.props.onDeleted();
+                if (this._unmounted) { return; }
+                this.setState({ deleting: false });
+            },
+            (e) => {
+                console.error("Error deleting device", e);
+                if (this._unmounted) { return; }
+                this.setState({
+                    deleting: false,
+                    deleteError: "Failed to delete device",
+                });
+            }
+        );
+    }
+
+    render() {
+        const EditableTextContainer = sdk.getComponent('elements.EditableTextContainer');
+
+        const device = this.props.device;
+
+        if (this.state.deleting) {
+            const Spinner = sdk.getComponent("elements.Spinner");
+
+            return (
+                <div className="mx_DevicesPanel_device">
+                    <Spinner />
+                </div>
+            );
+        }
+
+        let lastSeen = "";
+        if (device.last_seen_ts) {
+            // todo: format the timestamp as "5 minutes ago" or whatever.
+            const lastSeenDate = new Date(device.last_seen_ts);
+            lastSeen = device.last_seen_ip + " @ " +
+                lastSeenDate.toLocaleString();
+        }
+
+        let deleteButton;
+        if (this.state.deleteError) {
+            deleteButton = <div className="error">{this.state.deleteError}</div>
+        } else {
+            deleteButton = (
+                <div className="textButton"
+                  onClick={this._onDeleteClick}>
+                    Delete
+                </div>
+            );
+        }
+
+        return (
+            <div className="mx_DevicesPanel_device">
+                <div className="mx_DevicesPanel_deviceName">
+                    <EditableTextContainer initialValue={device.display_name}
+                        onSubmit={this._onDisplayNameChanged} />
+                </div>
+                <div className="mx_DevicesPanel_lastSeen">
+                    {lastSeen}
+                </div>
+                <div className="mx_DevicesPanel_deviceButtons">
+                    {deleteButton}
+                </div>
+            </div>
+        );
+    }
+}
+
+DevicesPanelEntry.propTypes = {
+    device: React.PropTypes.object.isRequired,
+    onDeleted: React.PropTypes.func,
+};
+
+DevicesPanelEntry.defaultProps = {
+    onDeleted: function() {},
+};