Skip to content

Instantly share code, notes, and snippets.

@andrewmcodes
Created July 1, 2026 04:50
Show Gist options
  • Select an option

  • Save andrewmcodes/e79a1eb76b84398a97400ba13abf48ef to your computer and use it in GitHub Desktop.

Select an option

Save andrewmcodes/e79a1eb76b84398a97400ba13abf48ef to your computer and use it in GitHub Desktop.
magic mirror
/* MagicMirror² Config — Kitchen, vertical (portrait) monitor
*
* Drop this in ~/MagicMirror/config/config.js
* Pair it with the custom.css in ~/MagicMirror/css/custom.css
*
* ─────────────────────────────────────────────────────────────
* CUSTOMIZE THESE FIRST (search for TODO)
* ─────────────────────────────────────────────────────────────
* 1. LAT / LON → your kitchen's coordinates (for weather)
* 2. units → "metric" (°C, km/h) or "imperial" (°F, mph)
* 3. timeFormat → 24 or 12
* 4. calendar url → swap the US-holidays feed for your own ICS
* 5. locale → e.g. "en-US", "en-GB", "de-DE"
* ─────────────────────────────────────────────────────────────
*/
let config = {
// Keep this on localhost unless you want to edit the mirror from
// another device on your LAN. To allow that, set address to "0.0.0.0"
// and add your subnet to ipWhitelist, e.g. "192.168.1.0/24".
address: "localhost",
port: 8080,
basePath: "/",
ipWhitelist: ["127.0.0.1", "::ffff:127.0.0.1", "::1"],
useHttps: false,
httpsPrivateKey: "",
httpsCertificate: "",
language: "en",
locale: "en-US", // TODO(5): your locale
timeFormat: 12, // 12-hour clock
units: "imperial", // °F, mph
logLevel: ["INFO", "LOG", "WARN", "ERROR"],
modules: [
// ── Housekeeping ──────────────────────────────────────────
{ module: "alert" },
{
module: "updatenotification",
position: "top_bar"
},
// ── TIME (big, read it across the room) ───────────────────
{
module: "clock",
position: "top_bar",
config: {
displayType: "digital",
displaySeconds: false,
showDate: true,
showWeek: false,
dateFormat: "dddd, MMMM Do" // e.g. "Tuesday, June 30th"
}
},
// ── WEATHER NOW ───────────────────────────────────────────
// Uses Open-Meteo: 100% free, NO API key needed. Just coords.
{
module: "weather",
position: "upper_third",
header: "Now",
config: {
weatherProvider: "openmeteo",
type: "current",
lat: 33.3062, // Chandler, AZ
lon: -111.8413, // Chandler, AZ
showHumidity: "wind_speed",
showFeelsLike: true
}
},
// ── FORECAST ──────────────────────────────────────────────
{
module: "weather",
position: "upper_third",
header: "This week",
config: {
weatherProvider: "openmeteo",
type: "daily",
lat: 33.3062, // Chandler, AZ
lon: -111.8413, // Chandler, AZ
maxNumberOfDays: 5,
fade: false
}
},
// ── CALENDAR (family / meal plan — glance while cooking) ──
{
module: "calendar",
header: "Upcoming",
position: "middle_center",
config: {
maximumEntries: 8,
maximumNumberOfDays: 14,
fade: false,
calendars: [
{
// Working default so the mirror shows something on boot.
// TODO(4): replace with YOUR calendar's *private* ICS URL.
// In Google Calendar: Settings → [your calendar] →
// "Secret address in iCal format".
symbol: "calendar-check",
url: "https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics"
}
]
}
},
// ── COMPLIMENTS / GREETING ────────────────────────────────
{
module: "compliments",
position: "lower_third",
config: {
updateInterval: 30000,
fadeSpeed: 4000,
compliments: {
morning: [
"Good morning, chef",
"Coffee first, decisions later",
"Let's make something good"
],
afternoon: [
"Snack o'clock?",
"Looking good in here",
"Hydrate, hero"
],
evening: [
"What's for dinner?",
"The kitchen is yours",
"Wind down, warm up"
]
}
}
},
// ── NEWS (bottom ticker) ──────────────────────────────────
{
module: "newsfeed",
position: "bottom_bar",
config: {
feeds: [
{
title: "BBC",
url: "https://feeds.bbci.co.uk/news/rss.xml"
}
],
showSourceTitle: true,
showPublishDate: true,
broadcastNewsFeeds: true,
broadcastNewsUpdates: true,
updateInterval: 15000
}
}
]
};
/*************** DO NOT EDIT BELOW ****************/
if (typeof module !== "undefined") { module.exports = config; }
/* MagicMirror² custom.css — Portrait / kitchen tuning
* Save as ~/MagicMirror/css/custom.css
*
* Assumes a monitor rotated 90° to a tall 1080 x 1920 canvas.
* Everything below is about making text legible from across a
* kitchen and using the vertical space sensibly.
*/
:root {
--color-text: #dfdfdf;
--color-text-dimmed: #8a8a8a;
--color-text-bright: #ffffff;
--font-size: 22px; /* base bump for read-from-the-stove distance */
}
body {
margin: 26px;
font-size: var(--font-size);
}
/* Give the stacked regions breathing room so they don't collide */
.region.top.bar,
.region.upper.third,
.region.middle.center,
.region.lower.third,
.region.bottom.bar {
width: 100%;
text-align: center;
}
.region .module {
margin-bottom: 34px;
}
/* CLOCK — the thing you actually walk in to check */
.clock .time {
font-size: 6.2rem;
font-weight: 300;
line-height: 1.05;
}
.clock .date {
font-size: 1.7rem;
color: var(--color-text-bright);
}
/* WEATHER — bigger icons + temps */
.weather .large.light,
.weather .weathericon {
font-size: 2.4rem;
}
.weather .normal.medium {
font-size: 1.25rem;
}
.weather table.small {
font-size: 1.15rem;
}
/* Section headers (Now / This week / Upcoming) */
.module-header {
font-size: 1.1rem;
letter-spacing: 2px;
color: var(--color-text-dimmed);
}
/* CALENDAR — roomy rows, no cramped truncation */
.calendar .event {
font-size: 1.2rem;
}
.calendar .time,
.calendar .symbol {
font-size: 1.2rem;
}
.calendar td {
padding: 6px 8px;
}
/* COMPLIMENTS — friendly, prominent */
.compliments .thin {
font-size: 2.1rem;
font-weight: 300;
}
/* NEWS — legible ticker pinned at the bottom */
.newsfeed .newsfeed-source,
.newsfeed .newsfeed-title {
font-size: 1.25rem;
}
/* Update banner shouldn't dominate */
.updatenotification { font-size: 0.9rem; }

Kitchen MagicMirror — Install Guide

Portrait (vertical) MagicMirror² for a Raspberry Pi kitchen display. Node is managed with mise. Weather uses Open-Meteo (no API key).

Files in this bundle:

  • config.js — the mirror layout (already set for Chandler, AZ / °F / 12h)
  • custom.css — portrait font/layout tuning

1. Install MagicMirror (mise-managed Node)

Current MagicMirror needs Node >=22.21.1 (or >=24). Let mise pin it rather than the bundled install script.

git clone https://github.com/MagicMirrorOrg/MagicMirror.git ~/MagicMirror
cd ~/MagicMirror

mise use node@22        # pins Node in ./mise.toml (use node@24 if you prefer)
mise install

mise exec -- node --run install-mm   # installs deps — takes a while, don't interrupt

2. Drop in the config + styling

cp config.js  ~/MagicMirror/config/config.js
cp custom.css ~/MagicMirror/css/custom.css

The only content TODO left in config.js: replace the US-holidays calendar feed with your own private ICS URL (Google Calendar → Settings → your calendar → "Secret address in iCal format").

3. Set the timezone (Arizona = no DST)

MagicMirror reads the system clock, so pin the zone that never springs forward:

sudo timedatectl set-timezone America/Phoenix
timedatectl        # confirm: America/Phoenix (MST, -0700)

4. First run

cd ~/MagicMirror && node --run start

(mise's shims make a plain node resolve to the pinned version inside this repo.) MagicMirror runs on Wayland by default now. If you're on X11 instead, use node --run start:x11.


5. Rotate the display to portrait

The old display_rotate in config.txt no longer rotates the desktop on current Raspberry Pi OS (Wayland/labwc). Two options:

GUI: desktop menu → Preferences → Screen Configuration → right-click the HDMI output → Orientation → left (or right).

CLI (permanent): find your output name, then set it in labwc autostart (covered in the next step). Check the name first:

wlr-randr          # note your output, usually HDMI-A-1 (sometimes HDMI-A-2)

6. Autostart on boot

Don't use system-level systemd here — it won't have the Wayland session's display env or mise activation. Put both the rotation and the launch in labwc's autostart, which already runs inside the graphical session:

# ~/.config/labwc/autostart
wlr-randr --output HDMI-A-1 --transform 90 &
cd $HOME/MagicMirror && $HOME/.local/bin/mise exec -- node --run start &

Notes:

  • labwc autostart runs under /bin/sh (not a login shell), so mise isn't auto-activated — that's why the full path $HOME/.local/bin/mise and mise exec are used (it reads the repo's mise.toml).
  • Match HDMI-A-1 to whatever wlr-randr reported.

Reboot to test:

sudo reboot

Troubleshooting

  • Blank 5-day forecast: change type: "daily" to type: "forecast" in the second weather block (some builds still use the older keyword).
  • Weather empty: re-check the lat/lon in both weather blocks. Open-Meteo needs no key, so a blank widget is almost always coordinates.
  • Clock/calendar times off: re-run the timedatectl step (Section 3).
  • Node version error on start: mise current inside ~/MagicMirror should show 22.x or 24.x; re-run mise install if not.
  • Rotation lost after monitor power-cycle: known Pi OS quirk — a reboot restores it; putting the display on standby (not full power-off) avoids it.

Optional kitchen add-ons

These need a git clone into ~/MagicMirror/modules plus a config block — ask and I'll wire them in:

  • MMM-GoogleTasks — shopping list on the mirror
  • A timer/pomodoro module — hands-free cooking timers
  • MMM-CalendarExt3 — a monthly grid view if you want a full calendar
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment