diff --git a/basicswap/http_server.py b/basicswap/http_server.py
index c3bc5ca..aeabbe1 100644
--- a/basicswap/http_server.py
+++ b/basicswap/http_server.py
@@ -161,24 +161,15 @@ class HttpHandler(BaseHTTPRequestHandler):
         content += '<p><a href="/">home</a></p></body></html>'
         return bytes(content, 'UTF-8')
 
-    def make_coin_select(self, name, coins):
-        s = '<select name="' + name + '"><option value="-1">-- Select Coin --</option>'
-        for c in coins:
-            s += '<option value="{}">{}</option>'.format(*c)
-        s += '</select>'
-        return s
-
     def page_newoffer(self, url_split, post_string):
         swap_client = self.server.swap_client
 
-        content = html_content_start(self.server.title, self.server.title) \
-            + '<h3>New Offer</h3>'
-
+        messages = []
         if post_string != '':
             form_data = urllib.parse.parse_qs(post_string)
             form_id = form_data[b'formid'][0].decode('utf-8')
             if self.server.last_form_id.get('newoffer', None) == form_id:
-                content += '<p>Prevented double submit for form {}.</p>'.format(form_id)
+                messages.append('Prevented double submit for form {}.'.format(form_id))
             else:
                 self.server.last_form_id['newoffer'] = form_id
 
@@ -206,28 +197,21 @@ class HttpHandler(BaseHTTPRequestHandler):
                     lock_type = ABS_LOCK_TIME
 
                 offer_id = swap_client.postOffer(coin_from, coin_to, value_from, rate, min_bid, SwapTypes.SELLER_FIRST, auto_accept_bids=autoaccept, lock_type=lock_type, lock_value=lock_seconds)
-                content += '<p><a href="/offer/' + offer_id.hex() + '">Sent Offer ' + offer_id.hex() + '</a><br/>Rate: ' + format8(rate) + '</p>'
+                messages.append('<a href="/offer/' + offer_id.hex() + '">Sent Offer ' + offer_id.hex() + '</a><br/>Rate: ' + format8(rate))
 
         coins = []
-
         for k, v in swap_client.coin_clients.items():
             if v['connection_type'] == 'rpc':
                 coins.append((int(k), getCoinName(k)))
 
-        content += '<form method="post">'
-
-        content += '<table>'
-        content += '<tr><td>Coin From</td><td>' + self.make_coin_select('coin_from', coins) + '</td><td>Amount From</td><td><input type="text" name="amt_from"></td></tr>'
-        content += '<tr><td>Coin To</td><td>' + self.make_coin_select('coin_to', coins) + '</td><td>Amount To</td><td><input type="text" name="amt_to"></td></tr>'
-
-        content += '<tr><td>Contract locked (hrs)</td><td><input type="number" name="lockhrs" min="2" max="96" value="48"></td><td colspan=2>Participate txn will be locked for half the time.</td></tr>'
-        content += '<tr><td>Auto Accept Bids</td><td colspan=3><input type="checkbox" name="autoaccept" value="aa" checked></td></tr>'
-        content += '</table>'
-
-        content += '<input type="submit" value="Submit">'
-        content += '<input type="hidden" name="formid" value="' + os.urandom(8).hex() + '"></form>'
-        content += '<p><a href="/">home</a></p></body></html>'
-        return bytes(content, 'UTF-8')
+        template = env.get_template('offer_new.html')
+        return bytes(template.render(
+            title=self.server.title,
+            h2=self.server.title,
+            messages=messages,
+            coins=coins,
+            form_id=os.urandom(8).hex(),
+        ), 'UTF-8')
 
     def page_offer(self, url_split, post_string):
         assert(len(url_split) > 2), 'Offer ID not specified'
@@ -240,56 +224,54 @@ class HttpHandler(BaseHTTPRequestHandler):
         offer = swap_client.getOffer(offer_id)
         assert(offer), 'Unknown offer ID'
 
-        content = html_content_start(self.server.title, self.server.title) \
-            + '<h3>Offer: ' + offer_id.hex() + '</h3>'
-
+        messages = []
+        sent_bid_id = None
         if post_string != '':
             form_data = urllib.parse.parse_qs(post_string)
             form_id = form_data[b'formid'][0].decode('utf-8')
             if self.server.last_form_id.get('offer', None) == form_id:
-                content += '<p>Prevented double submit for form {}.</p>'.format(form_id)
+                messages.append('Prevented double submit for form {}.'.format(form_id))
             else:
                 self.server.last_form_id['offer'] = form_id
-                bid_id = swap_client.postBid(offer_id, offer.amount_from)
-                content += '<p><a href="/bid/' + bid_id.hex() + '">Sent Bid ' + bid_id.hex() + '</a></p>'
+                sent_bid_id = swap_client.postBid(offer_id, offer.amount_from).hex()
 
         coin_from = Coins(offer.coin_from)
         coin_to = Coins(offer.coin_to)
         ticker_from = swap_client.getTicker(coin_from)
         ticker_to = swap_client.getTicker(coin_to)
-
-        tr = '<tr><td>{}</td><td>{}</td></tr>'
-        content += '<table>'
-        content += tr.format('Offer State', getOfferState(offer.state))
-        content += tr.format('Coin From', getCoinName(coin_from))
-        content += tr.format('Coin To', getCoinName(coin_to))
-        content += tr.format('Amount From', format8(offer.amount_from) + ' ' + ticker_from)
-        content += tr.format('Amount To', format8((offer.amount_from * offer.rate) // COIN) + ' ' + ticker_to)
-        content += tr.format('Rate', format8(offer.rate) + ' ' + ticker_from + '/' + ticker_to)
-        content += tr.format('Script Lock Type', getLockName(offer.lock_type))
-        content += tr.format('Script Lock Value', offer.lock_value)
-        content += tr.format('Address From', offer.addr_from)
-        content += tr.format('Created At', time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(offer.created_at)))
-        content += tr.format('Expired At', time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(offer.expire_at)))
-        content += tr.format('Sent', 'True' if offer.was_sent else 'False')
+        data = {
+            'tla_from': swap_client.getTicker(coin_from),
+            'tla_to': swap_client.getTicker(coin_to),
+            'state': getOfferState(offer.state),
+            'coin_from': getCoinName(coin_from),
+            'coin_to': getCoinName(coin_to),
+            'amt_from': format8(offer.amount_from),
+            'amt_to': format8((offer.amount_from * offer.rate) // COIN),
+            'rate': format8(offer.rate),
+            'lock_type': getLockName(offer.lock_type),
+            'lock_value': offer.lock_value,
+            'addr_from': offer.addr_from,
+            'created_at': offer.created_at,
+            'expired_at': offer.expire_at,
+            'sent': 'True' if offer.was_sent else 'False'
+        }
 
         if offer.was_sent:
-            content += tr.format('Auto Accept Bids', 'True' if offer.auto_accept_bids else 'False')
-        content += '</table>'
+            data['auto_accept'] = 'True' if offer.auto_accept_bids else 'False'
 
         bids = swap_client.listBids(offer_id=offer_id)
 
-        content += '<h4>Bids</h4><table>'
-        content += '<tr><th>Bid ID</th><th>Bid Amount</th><th>Bid Status</th><th>ITX Status</th><th>PTX Status</th></tr>'
-        for b in bids:
-            content += '<tr><td><a href=/bid/{0}>{0}</a></td><td>{1}</td><td>{2}</td><td>{3}</td><td>{4}</td></tr>'.format(b.bid_id.hex(), format8(b.amount), getBidState(b.state), getTxState(b.initiate_txn_state), getTxState(b.participate_txn_state))
-        content += '</table>'
-
-        content += '<form method="post">'
-        content += '<input type="submit" value="Send Bid">'
-        content += '<input type="hidden" name="formid" value="' + os.urandom(8).hex() + '"></form>'
-        content += '<p><a href="/">home</a></p></body></html>'
-        return bytes(content, 'UTF-8')
+        template = env.get_template('offer.html')
+        return bytes(template.render(
+            title=self.server.title,
+            h2=self.server.title,
+            offer_id=offer_id.hex(),
+            sent_bid_id=sent_bid_id,
+            messages=messages,
+            data=data,
+            bids=[(b.bid_id.hex(), format8(b.amount), getBidState(b.state), getTxState(b.initiate_txn_state), getTxState(b.participate_txn_state)) for b in bids],
+            form_id=os.urandom(8).hex(),
+        ), 'UTF-8')
 
     def page_offers(self, url_split, sent=False):
         swap_client = self.server.swap_client
diff --git a/basicswap/templates/offer.html b/basicswap/templates/offer.html
new file mode 100644
index 0000000..7e27891
--- /dev/null
+++ b/basicswap/templates/offer.html
@@ -0,0 +1,48 @@
+{% include 'header.html' %}
+
+<h3>Offer {{ offer_id }}</h3>
+{% if refresh %}
+<p>Page Refresh: {{ refresh }} seconds</p>
+{% endif %}
+
+{% for m in messages %}
+<p>{{ m }}</p>
+{% endfor %}
+
+{% if sent_bid_id %}
+<p><a href="/bid/{{ sent_bid_id }}">Sent Bid {{ sent_bid_id }}</a></p>
+{% endif %}
+
+<table>
+<tr><td>Offer State</td><td>{{ data.state }}</td></tr>
+<tr><td>Coin From</td><td>{{ data.coin_from }}</td></tr>
+<tr><td>Coin To</td><td>{{ data.coin_to }}</td></tr>
+<tr><td>Amount From</td><td>{{ data.amt_from }} {{ data.tla_from }}</td></tr>
+<tr><td>Amount To</td><td>{{ data.amt_to }} {{ data.tla_to }}</td></tr>
+<tr><td>Rate</td><td>{{ data.rate }} {{ data.amt_from }}/{{ data.tla_from }}</td></tr>
+<tr><td>Script Lock Type</td><td>{{ data.lock_type }}</td></tr>
+<tr><td>Script Lock Value</td><td>{{ data.lock_value }}</td></tr>
+<tr><td>Address From</td><td>{{ data.addr_from }}</td></tr>
+<tr><td>Created At</td><td>{{ data.created_at | formatts }}</td></tr>
+<tr><td>Expired At</td><td>{{ data.expired_at | formatts }}</td></tr>
+<tr><td>Sent</td><td>{{ data.sent }}</td></tr>
+{% if data.sent == 'True' %}
+<tr><td>Auto Accept Bids</td><td>{{ data.auto_accept }}</td></tr>
+{% endif %}
+</table>
+
+<h4>Bids</h4>
+<table>
+<tr><th>Bid ID</th><th>Bid Amount</th><th>Bid Status</th><th>ITX Status</th><th>PTX Status</th></tr>
+{% for b in bids %}
+<tr><td><a href=/bid/{{ b[0] }}>{{ b[0] }}</a></td><td>{{ b[1] }}</td><td>{{ b[2] }}</td><td>{{ b[3] }}</td><td>{{ b[4] }}</td></tr>
+{% endfor %}
+</table>
+
+<form method="post">
+<input type="submit" value="Send Bid">
+<input type="hidden" name="formid" value="{{ form_id }}">
+</form>
+
+<p><a href="/">home</a></p>
+</body></html>
diff --git a/basicswap/templates/offer_new.html b/basicswap/templates/offer_new.html
new file mode 100644
index 0000000..3f95738
--- /dev/null
+++ b/basicswap/templates/offer_new.html
@@ -0,0 +1,36 @@
+{% include 'header.html' %}
+
+<h3>New Offer</h3>
+{% for m in messages %}
+<p>{{ m }}</p>
+{% endfor %}
+
+<form method="post">
+
+<table>
+<tr><td>Coin From</td><td>
+<select name="coin_from"><option value="-1">-- Select Coin --</option>
+{% for c in coins %}
+<option value="{{ c[0] }}">{{ c[1] }}</option>
+{% endfor %}
+</select>'
+</td><td>Amount From</td><td><input type="text" name="amt_from"></td></tr>
+
+<tr><td>Coin To</td><td>
+<select name="coin_to"><option value="-1">-- Select Coin --</option>
+{% for c in coins %}
+<option value="{{ c[0] }}">{{ c[1] }}</option>
+{% endfor %}
+</select>'
+</td><td>Amount To</td><td><input type="text" name="amt_to"></td></tr>
+
+<tr><td>Contract locked (hrs)</td><td><input type="number" name="lockhrs" min="2" max="96" value="48"></td><td colspan=2>Participate txn will be locked for half the time.</td></tr>
+<tr><td>Auto Accept Bids</td><td colspan=3><input type="checkbox" name="autoaccept" value="aa" checked></td></tr>
+</table>
+
+<input type="submit" value="Submit">
+<input type="hidden" name="formid" value="{{ form_id }}">
+</form>
+
+<p><a href="/">home</a></p>
+</body></html>