Widget Examples

Watching - this is a great idea - hope it’s implementable!

I’ve been a bit obsessed with this on the Mac

Would you mind sharing your script?

A combination script and couple of shortcuts to check on CitiBike docks in particular locations (where I am, where I am going). This is the bike share system in New York City. Would probably work for other systems that use the GBFS standard. Will probably add some more interesting functionality for on-tap.

21 posts were split to a new topic: Custom Calendar Widget

I would like to see an example of the tapping functionality. Also I have a question, is the (+ Note) button tappable?

I had the same issue with my xkcd widget. I made a separate function to solve it. it’s an async function though but not too much of an issue.

async function isUsingDarkAppearance() {
  const wv = new WebView()
  let js ="(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches)"
  let r = await wv.evaluateJavaScript(js)
  return r
1 Like

A widget showing a random painting from the Metropolitan Museum of Art.
If you click on it you see the details in your browser.

Code here

//Department ID = 11 is paintings ; use other if you wish

const url = 'https://collectionapi.metmuseum.org/public/collection/v1/search?hasImages=true&departmentId=11&q=Painting'
const req = new Request(url)
const res = await req.loadJSON()
const max = res.total-1
console.log("Max: "+max);
const min = 0
const random = Math.floor(Math.random() * (max - min + 1)) + min
console.log("Random: "+random);
const picked = res.objectIDs[random]
console.log("Picked: "+picked);
const req2 = new Request('https://collectionapi.metmuseum.org/public/collection/v1/objects/'+picked)
const res2 = await req2.loadJSON()
const i = new Request(res2.primaryImageSmall);
const img = await i.loadImage();

let widget = createWidget(img, res2.objectURL)
if (config.runsInWidget) {
  // create and show widget
else {

function createWidget(img, widgeturl) {
  let w = new ListWidget()
  w.backgroundColor = new Color("#1A1A1A")
  w.backgroundImage = img
  w.url = widgeturl
  return w

Can you please share your script?

For the guys asking about the code: I did not post it because this was just a quick hack and not clean code.

On top of that I currently have an issue with this script after cleaning up the code a bit by putting some common code in a library. The import of that library sometimes fails due to lack of permissions when viewing the widget (workaround: start the Scriptable App).

const soccer = importModule('lib/Soccer')

@simonbs Is this a widget issue? A Scriptable bug? Or did I do something wrong?

I might post a cleaned up variant later, but if not: the crucial part for people that want to write a similar widget is a GET request with this URL:


Here 678 is the ID for Ajax; you would need the ID of your own favourite team in your script.

(one of the things I still need to improve is that I currently just take the first entry in the returned array; but it would be safer if I sort that array on the match date first, because I don’t know whether correct order is guaranteed in the result)

PS: Additionally I would want to display the team’s club logos; so I’m looking forward to the stack view feature Simon announced on Twitter!


I created a widget for tracking pending reviews on Wanikani (a kanji learning site).

It shows my current level and the number of reviews I need to clear. Tapping the widget opens wanikani.com in the browser for me to get to work.

// web constants
const apiKey = "API-KEY-GOES-HERE"
const auth = "Bearer " + apiKey
const waniKaniRev = "20170710"

const userURL = "https://api.wanikani.com/v2/user"
const summaryURL = "https://api.wanikani.com/v2/summary"
const imageURL = "https://adomainnamegoeshere.com/apathgoeshere/animagegoeshere.png"

const waniKaniURL = "https://wanikani.com/"

// create the web request to the wanikani api
let userRequest = new Request(userURL)
userRequest.headers = {"Authorization": auth, "Wanikani-Revision": waniKaniRev};

// get the current user level from the user account info
let user = await userRequest.loadJSON()
let myLevel = user.data.level

// get the current number of pending reviews from the summary info
userRequest.url = summaryURL
let summary = await userRequest.loadJSON()
let myReviewCount = summary.data.reviews[0].subject_ids.length

// create the widget
let waniKaniWidget = new ListWidget()

// set the background image and color
waniKaniWidget.backgroundColor = new Color("#2c94f5")
let waniKaniImage = await loadImage(imageURL)
waniKaniWidget.backgroundImage = waniKaniImage

// add the level title and value
let levelLabel = waniKaniWidget.addText("Level")
levelLabel.font = Font.boldSystemFont(16)
levelLabel.textColor = new Color("#ffffff")

let levelNotice = waniKaniWidget.addText(myLevel.toString())
levelNotice.font = Font.boldSystemFont(40)
levelNotice.textColor = new Color("#00ff2f")


// add the reviews pending title and value
let reviewsLabel = waniKaniWidget.addText("Reviews")
reviewsLabel.font = Font.boldSystemFont(16)
reviewsLabel.textColor = new Color("#ffffff")

let reviewsCount = waniKaniWidget.addText(myReviewCount.toString())
reviewsCount.font = Font.boldSystemFont(40)
reviewsCount.textColor = new Color("#dafd32")

waniKaniWidget.url = waniKaniURL

if (config.runsInWidget) {	
} else {	

async function loadImage(imageUrl) {
  let req = new Request(imageUrl)
  let image = await req.loadImage()
	if (image != null) {
		return image
	} else {
		console.log("something happened")
1 Like

J, this is an amazing widget! I’ve been playing with it, but for the life of me, I can’t solve one problem. (I have close to no experience with this) When the system is in light mode, the font color flips to black on black. I’ve tried setting the color a few different ways and selecting specific fonts, but it changes to black on black every time the system theme changes. Any idea how to keep it white on black?

Hi cranie, thanks for your code, I’m highly interested in this and tried it myself but got this error. Do you know how to fix this?

Thanks for anyone chiming in. I discovered this just today and absolutely love it.

I got the alignment errors to go away by signing up for the beta. :man_shrugging:t2:

The centerAlignContent(), topAlignContent() and bottomAlignContent() functions were introduced during the beta of 1.5 but was also removed during the beta period. They were replaced with spacers.

Spacers add fixed or flexible spacing to a widget. Adding a spacer before other content in the widget, will push the content down to the bottom. This is equal to (the now removed) bottomAlignContent() function. Now if you instead added a spacer below the content, it would be pushed to the top, which is equal to topAlignContent().

Replacing these fairly straight forward functions with spacers might seem like an odd decision at first. Spacers are more complicated but also offer way more flexibility. For example, if you build a widget like this:

Text A
Text B

Now you’ll get Text A aligned in the top and Text B aligned in the bottom. That wasn’t possible with the older functions.

This is just the top of the ice berg for spacers. They become much more important in the next update :smiley:

1 Like

I am very new to this and have a question or two. Would it be possible to create a widget that launched iOS apps? For example, if you just has a widget with a text list of apps and you click on the name of an app, could it then launch the app? Or would the text be too small for the phone to reliably know what text you are touching?

Right now I believe 3rd party apps can’t have their widgets have multiple tap targets. You could, on tap run a script or shortcut which then displays a menu of apps to open.

1 Like

Thanks! I will give that a try.

@Matt_Hood @dustinknopoff Multiple tap targets will be supported in the next update to Scriptable :blush:


You could launch apps with URL schemes right?

Thank you for the clarification Simon!