The simple and cool solution here is to put your ELB behind CloudFront.
If the origin server (the ELB in this case) throws a 5XX error (or 4XX if you like), CloudFront can return a custom error page, which you can configure CloudFront to fetch from an S3 bucket by creating a second origin pointing to the bucket and creating a cache behavior routing (e.g.) /errors/static/*
to the bucket.
This works better than Route 53 failover for an important reason... a fatal flaw, if you will... browsers are terrible about caching DNS lookups for far longer than you expect. The DNS TTL isn't relevant.
Essentially, once a browser has a DNS entry in hand, it just keeps trying to use it... typically, until all browser windows are closed.
So if your site goes down for a visitor who was already on the site, they unlikely to see the alternate site.
Worse, if a visitor hits your site for the first time while it's down, they'll "stick" on the maintenance page until they close all browser windows.
If you use failover DNS, that's really only good if the failover target is still your application, maybe just further away.
You can turn CloudFront's caching off if you don't need it.
You can also configure CloudFront's error caching TTL to a nonzero value if you want it to quit hammering your site while it's down and trying to recover. For a given page that throws an error, it will keep showing the error page and not bother your server with more requests for that page until the Error CachingTTL expires.