diff --git a/src/components/settings/DnsList.js b/src/components/settings/DnsList.js
new file mode 100644
index 0000000..5ac16a3
--- /dev/null
+++ b/src/components/settings/DnsList.js
@@ -0,0 +1,50 @@
+/* Pi-hole: A black hole for Internet advertisements
+* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
+* Network-wide ad blocking via your own hardware.
+*
+* Web Interface
+* An Editable List of Upstream DNS Servers
+*
+* This file is copyright under the latest version of the EUPL.
+* Please see LICENSE file for your rights under this license. */
+
+import React from "react";
+import PropTypes from "prop-types";
+import { ListGroup } from "reactstrap";
+import DnsListItem from "./DnsListItem";
+import DnsListNewItem from "./DnsListNewItem";
+import { isValidIpv4OptionalPort } from "../../validate";
+
+/**
+ * Check if an upstream address is unique and valid
+ *
+ * @param address the address to check
+ * @param upstreams the list of current upstreams
+ * @returns {boolean} if the upstream address is unique and valid
+ */
+export const isAddressValid = (address, upstreams) =>
+ !upstreams.includes(address) && isValidIpv4OptionalPort(address);
+
+const DnsList = ({ upstreams, onAdd, onRemove }) => (
+
+ {upstreams.map(upstream => (
+ onRemove(upstream)}
+ address={upstream}
+ />
+ ))}
+ isAddressValid(address, upstreams)}
+ />
+
+);
+
+DnsList.propTypes = {
+ upstreams: PropTypes.arrayOf(PropTypes.string).isRequired,
+ onAdd: PropTypes.func.isRequired,
+ onRemove: PropTypes.func.isRequired
+};
+
+export default DnsList;
diff --git a/src/components/settings/DnsListItem.js b/src/components/settings/DnsListItem.js
new file mode 100644
index 0000000..276c50c
--- /dev/null
+++ b/src/components/settings/DnsListItem.js
@@ -0,0 +1,29 @@
+/* Pi-hole: A black hole for Internet advertisements
+* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
+* Network-wide ad blocking via your own hardware.
+*
+* Web Interface
+* A Upstream DNS List Item
+*
+* This file is copyright under the latest version of the EUPL.
+* Please see LICENSE file for your rights under this license. */
+
+import React from "react";
+import PropTypes from "prop-types";
+import { Button, ListGroupItem } from "reactstrap";
+
+const DnsListItem = ({ address, onRemove }) => (
+
+ {address}
+
+
+);
+
+DnsListItem.propTypes = {
+ address: PropTypes.string.isRequired,
+ onRemove: PropTypes.func.isRequired
+};
+
+export default DnsListItem;
diff --git a/src/components/settings/DnsListNewItem.js b/src/components/settings/DnsListNewItem.js
new file mode 100644
index 0000000..173545b
--- /dev/null
+++ b/src/components/settings/DnsListNewItem.js
@@ -0,0 +1,60 @@
+/* Pi-hole: A black hole for Internet advertisements
+* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
+* Network-wide ad blocking via your own hardware.
+*
+* Web Interface
+* Input For New DNS List Items
+*
+* This file is copyright under the latest version of the EUPL.
+* Please see LICENSE file for your rights under this license. */
+
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import {
+ Button,
+ Input,
+ InputGroup,
+ InputGroupAddon,
+ ListGroupItem
+} from "reactstrap";
+
+export default class DnsListNewItem extends Component {
+ static propTypes = {
+ onAdd: PropTypes.func.isRequired,
+ isValid: PropTypes.func.isRequired
+ };
+
+ state = {
+ address: ""
+ };
+
+ render() {
+ const isAddressValid = this.props.isValid(this.state.address);
+
+ return (
+
+
+ this.setState({ address: e.target.value })}
+ />
+
+
+
+
+
+ );
+ }
+}