AnkiDroid Javascript API - ankidroid/Anki-Android GitHub Wiki

JavaScript API for calling native AnkiDroid functions inside WebView

This page is an advanced feature, designed for deck developers

This api allows deck developer to add functionality to cards that can call native functions defined in AnkiDroid. This can be used to design new layouts for buttons, cards info, top bar card counts, mark, flag etc.

Additional information:

API Setup

The API must be initialized before use. You must provide a developer contact before using the API. Your contact details are shown to users if breaking changes are made to the API.

The v2.18 api version is 0.0.3. 2.17 was 0.0.2.

<script>
   // A debug console is strongly recommended: https://github.com/ankidroid/Anki-Android/wiki/Development-Guide#html-javascript-inspection
   var jsApiContract = { version: "0.0.3", developer: /* your email here */ };
   var api = new AnkiDroidJS(jsApiContract);
</script>

Responses

API responses return an API Result wrapped in a Promise. Pseudocode:

type ApiResult = { success: boolean, value: boolean | string | number | any[] | any }
type ApiResponse = Promise<ApiResultData>

On success, response.value is usable.

<script>
   // A debug console is strongly recommended: https://github.com/ankidroid/Anki-Android/wiki/Development-Guide#html-javascript-inspection
   var jsApiContract = { version: "0.0.3", developer: /* your email here */ };
   var api = new AnkiDroidJS(jsApiContract);
   api.ankiGetCardDid().then(response => console.log(response)); // {success: true, value: '1707102058557'}
   // or use await in an IIFE (https://developer.mozilla.org/en-US/docs/Glossary/IIFE): 
   (async function() {
       console.log(await api.ankiGetCardDid()); // {success: true, value: '1707102058557'}
   })();
   // the response may not need to be accessed
   api.ankiShowToast("Hello Anki!");
</script>

Failures

Expect API failures, these are most likely to occur when functions are not available in the Previewer (e.g. ankiGetETA()), but will also occur when breaking API changes are made.

When a failure occurs, type-safe default values are provided in ApiResult.value, along with { success: false }

invalidApi.ankiGetCardDid("").then(x => console.log(x)) // {success: false, value: '-1'}
  • boolean => false
  • number => -1
  • string => "unknown"
  • array => []
  • object => {}

API Methods

A list of all methods can be obtained with console.log(Object.keys(jsApiList).sort().join("\r\n"))

Full listing

ankiAnswerEase1
ankiAnswerEase2
ankiAnswerEase3
ankiAnswerEase4
ankiBuryCard
ankiBuryNote
ankiEnableHorizontalScrollbar
ankiEnableVerticalScrollbar
ankiGetCardDid
ankiGetCardDue
ankiGetCardFactor
ankiGetCardFlag
ankiGetCardId
ankiGetCardInterval
ankiGetCardLapses
ankiGetCardLeft
ankiGetCardMark
ankiGetCardMod
ankiGetCardNid
ankiGetCardODid
ankiGetCardODue
ankiGetCardQueue
ankiGetCardReps
ankiGetCardType
ankiGetDeckName
ankiGetETA
ankiGetLrnCardCount
ankiGetNewCardCount
ankiGetNextTime1
ankiGetNextTime2
ankiGetNextTime3
ankiGetNextTime4
ankiGetRevCardCount
ankiIsActiveNetworkMetered
ankiIsDisplayingAnswer
ankiIsInFullscreen
ankiIsInNightMode
ankiIsTopbarShown
ankiMarkCard
ankiResetProgress
ankiSearchCard
ankiSearchCardWithCallback
ankiSetCardDue
ankiShowAnswer
ankiShowNavigationDrawer
ankiShowOptionsMenu
ankiShowToast
ankiSttSetLanguage
ankiSttStart
ankiSttStop
ankiSuspendCard
ankiSuspendNote
ankiToggleFlag
ankiTtsFieldModifierIsAvailable
ankiTtsIsSpeaking
ankiTtsSetLanguage
ankiTtsSetPitch
ankiTtsSetSpeechRate
ankiTtsSpeak
ankiTtsStop

Show Answer

  • Name

showAnswer()

  • Info

When a card is shown, only the question is shown at first. So use this to perform show answer click through JS.

  • Usage

<button onclick="showAnswer();">Show Answer From JavaScript</button>

Again/Hard/Good/Easy

The following buttons can be called when available, there is case where only Again and Hard present so that can also be handled using JavaScript code. The following function will be called when buttons available on screen.

Again

  • Name

buttonAnswerEase1()

  • Info

Perform Again button click

  • Usage

<button onclick="buttonAnswerEase1();">Again From JS</button>

Hard

  • Name

buttonAnswerEase2()

  • Info

Perform Hard button click

  • Usage

<button onclick="buttonAnswerEase2();">Hard From JS</button>

Good

  • Name

buttonAnswerEase3()

  • Info

Perform Good button click

  • Usage

<button onclick="buttonAnswerEase3();">Good From JS</button>

Easy

  • Name

buttonAnswerEase4()

  • Info

Perform Easy button click

  • Usage

<button onclick="buttonAnswerEase4();">Easy From JS</button>

Reschedule card with x days

  • Name

api.ankiSetCardDue(int)

  • Info

Reschedule card with x days. This will set the interval of the card to x (cf this issue)

  • Usage

<button onclick="api.ankiSetCardDue(5);">Reschedule card in 5 days</button>

Mark / Unmark current card

  • Name

api.ankiMarkCard()

  • Info

Adds a tag called "Marked" the current note, so it can be easily found in a search.

  • Usage

<button onclick="api.ankiMarkCard();">Mark</button>

Flag / Remove flag in current card

  • Name

api.ankiToggleFlag()

  • Info

Adds a colored marker to the card, or toggles it off. Flags will appear during study, and it can be easily found in a search.
Pass the arguments "none", "red", "orange", "green", "blue" for flagging respective flag.

none,
red,
orange,
green,
blue,

For flagging red in current card.

  • Usage

<button onclick="api.ankiToggleFlag('red');">Red Flag</button>

Bury Cards

  • Name

api.ankiBuryCard();
  • Return

boolean

  • Info

Bury current Cards showing in reviewer UI

  • Usage

<button onclick="api.ankiBuryCard();">Bury Card</button>

Bury Notes

  • Name

api.ankiBuryNote();
  • Info

Bury current Notes showing in reviewer UI

  • Return

boolean

  • Usage

<button onclick="api.ankiBuryNote();">Bury Note</button>

Suspend Cards

  • Name

api.ankiSuspendCard();
  • Return

boolean

  • Info

Suspend current Cards showing in reviewer UI

  • Usage

<button onclick="api.ankiSuspendCard();">Suspend Card</button>

Suspend Notes

  • Name

api.ankiSuspendNote();
  • Return

boolean

  • Info

Suspend current Notes showing in reviewer UI

  • Usage

<button onclick="api.ankiSuspendNote();">Suspend Note</button>

Tag Cards

  • Name

api.ankiAddTagToCard();
  • Info

Open tags dialog to add tags to current Notes

  • Usage

<button onclick="api.ankiAddTagToCard();">Show Tag Dialog to add tag</button>

Show options menu using JavaScript

  • Name

ankiShowOptionsMenu()

  • Info

In full screen, a button can be added to show options menu.

  • Usage

<button onclick="ankiShowOptionsMenu()">Show Options Menu</button>

Show navigation drawer using JavaScript

  • Name

ankiShowNavDrawer()

  • Info

In full screen, a button can be added to show side navigation drawer.

  • Usage

<button onclick="ankiShowNavDrawer()">Show Navigation Drawer</button>

Show Toast

  • Name

ankiShowToast(message)

  • Info

Show Android Toast message

  • Usage

ankiShowToast("This message will shown in reviewer");

Get status if showing question or answer

  • Name

api.ankiIsDisplayingAnswer()

  • Type of return value

Boolean

  • Info

Return true if reviewer showing answer else false

  • Usage

console.log(api.ankiIsDisplayingAnswer());

Get fullscreen status

  • Name

api.ankiIsInFullscreen()

  • Type of return value

Boolean

  • Info

Return fullscreen status in webview

  • Usage

console.log(api.ankiIsInFullscreen());

Get show / hide status of topbar

  • Name

api.ankiIsTopbarShown()

  • Type of return value

Boolean

  • Info

It can be used to show / hide custom topbar design. See #6747 for more.

  • Usage

console.log(api.ankiIsTopbarShown());

Night mode mode status

  • Name

api.ankiIsInNightMode()

  • Type of return value

Boolean

  • Info

Get night mode status. It can used to toggle between custom css based on the status.

  • Usage

console.log(api.ankiIsInNightMode());

Get status about metered connection

  • Name

api.ankiIsActiveNetworkMetered()

  • Type of return value

Boolean

  • Info

Status about device connected to metered connection. It can be used stop loading heavy assets.

  • Usage

console.log(api.ankiIsActiveNetworkMetered());

Available information about current cards in WebView

Add functions to Front / Back side of card to get info.

New card count

  • Name

api.ankiGetNewCardCount()

  • Type of return value

int

  • Info

Return number of new card count

  • Usage

console.log(api.ankiGetNewCardCount());

Learn card count

  • Name

api.ankiGetLrnCardCount()

  • Type of return value

int

  • Info

Return number of learn card count

  • Usage

console.log(api.ankiGetLrnCardCount());

Review card count

  • Name

api.ankiGetRevCardCount()

  • Type of return value

int

  • Info

Return number of review card count

  • Usage

console.log(api.ankiGetRevCardCount());

ETA

  • Name

api.ankiGetETA()

  • Type of return value

int

  • Info

Return remaining time to complete review

  • Usage

console.log(api.ankiGetETA());

Mark status

  • Name

api.ankiGetCardMark()

  • Type of return value

true/false

  • Info

Return current card marked or not. Return boolean value of mark status, true for marked, false for unmarked

  • Usage

console.log(api.ankiGetCardMark());

Flag status

  • Name

api.ankiGetCardFlag()

  • Type of return value

int

  • Info

Return int value of flag

none, 
red, 
orange, 
green, 
blue
  • Usage

console.log(api.ankiGetCardFlag());

Card ID

  • Name

api.ankiGetCardId()

  • Type of return value

long

  • Info

Returns the ID of the card. Example: 1477380543053

  • Usage

console.log(api.ankiGetCardId());

Notes ID

  • Name

api.ankiGetCardNid()

  • Type of return value

long

  • Info

Returns the ID of the note which generated the card. Example: 1590418157630

  • Usage

console.log(api.ankiGetCardNid());

Deck ID

  • Name

api.ankiGetCardDid()

  • Type of return value

long

  • Info

Returns the ID of the deck which contains the card. Example: 1595967594978

  • Usage

console.log(api.ankiGetCardDid());

Get deck name

  • Name

api.ankiGetDeckName()

  • Type of return value

String

  • Info

Return deck name currently showing in webview/reviewer

  • Usage

console.log(api.ankiGetDeckName());

Last modified time of card

  • Name

api.ankiGetCardMod()

  • Type of return value

long

  • Info

Returns the last modified time as a Unix timestamp in seconds. Example: 1477384099

If a card was never modified, this does NOT give the time when it was added, but some other unknown value, which does not seem to be in relation to to the added time. Maybe it is like the return value of the "cardDue" API method which returns "note id or random int" for new cards.

  • Usage

console.log(api.ankiGetCardMod());

Card Type

  • Name

api.ankiGetCardType()

  • Type of return value

int

  • Info

Returns card type

0 = new
1 = learning 
2 = review 
3 = relearning
  • Usage

console.log(api.ankiGetCardType());

Queue

  • Name

api.ankiGetCardQueue()

  • Type of return value

int

  • Info

 -3 = user buried(In scheduler 2),
 -2 = sched buried (In scheduler 2), 
 -2 = buried(In scheduler 1),
 -1 = suspended,
  0 = new, 1=learning, 2=review (as for type)
  3 = in learning, next rev in at least a day after the previous review
  4 = preview
  • Usage

console.log(api.ankiGetCardQueue());

Card Left

  • Name

api.ankiGetCardLeft()

  • Type of return value

int

  • Info

-- of the form a*1000+b, with:
-- b the number of reps left till graduation
-- a the number of reps left today
  • Usage

console.log(api.ankiGetCardLeft());

Due

  • Name

api.ankiGetCardDue()

  • Type of return value

long

  • Info

Due is used differently for different card types:

new: note id or random int
due: integer day, relative to the collection's creation time
learning: integer timestamp
  • Usage

console.log(api.ankiGetCardDue());

Interval

  • Name

api.ankiGetCardInterval()

  • Type of return value

int

  • Info

interval (used in SRS algorithm). Negative = seconds, positive = days

  • Usage

console.log(api.ankiGetCardInterval());

Card Ease Factor

  • Name

api.ankiGetCardFactor()

  • Type of return value

int

  • Info

Return ease factor of the card in permille (parts per thousand)

  • Usage

console.log(api.ankiGetCardFactor());

Review

  • Name

api.ankiGetCardReps()

  • Type of return value

int

  • Info

Return number of reviews made on current card

  • Usage

console.log(api.ankiGetCardReps());

Lapses

  • Name

api.ankiGetCardLapses()

  • Type of return value

int

  • Info

Return number of times the card went from a "was answered correctly"

  • Usage

console.log(api.ankiGetCardLapses());

Original Due

  • Name

api.ankiGetCardODue()

  • Type of return value

long

  • Info

original due: In filtered decks, it's the original due date that the card had before moving to filtered.
                    -- If the card lapsed in scheduler1, then it's the value before the lapse. (This is used when switching to scheduler 2. At this time, cards in learning becomes due again, with their previous due date)
                    -- In any other case it's 0.
  • Usage

console.log(api.ankiGetCardODue());

Deck ID of home deck if filtered

  • Name

api.ankiGetCardODid()

  • Type of return value

long

  • Info

Returns the ID of the home deck for the card if it is filtered, or 0 if not filtered. Example: 1595967594978

  • Usage

console.log(api.ankiGetCardODid());

Get next time for review in WebView

  • Name

api.ankiGetNextTime1()
api.ankiGetNextTime2()
api.ankiGetNextTime3()
api.ankiGetNextTime4()
  • Type of return value

String

  • Info

Return time for next review. Time at answer buttons (Again/Hard/Good/Easy/). It can be used to hide button if returned string is empty.

  • Usage

console.log(api.ankiGetNextTime1());
console.log(api.ankiGetNextTime2());
console.log(api.ankiGetNextTime3());
console.log(api.ankiGetNextTime4());
  • Bug

This has a bug in v2.18. I use this to correct:

  public static async nextTimeStringForButton(i: number):Promise<string> {
    if (Anki.mock)
      return "1mocked"

    const returnValueFromApi = await Anki.nextTimeStringForButtonRaw(i)

    let corrected: JsApiString

    const retType = typeof returnValueFromApi

    if (retType==="string") {
      corrected = JSON.parse(returnValueFromApi)
    } else if (retType==="object")
      corrected = returnValueFromApi.value
    else
      throw new Error("nextTimeStringForButton: unexpected return type")

    if (!corrected.success)
      printDebug("JS API returned success===false")

    return corrected.value
  }

  private static wrap = (value: unknown) => {
    return {value: value}
  }

  private static nextTimeStringForButtonRaw = async (i: number) => {
    if (Anki.mock)
      return this.wrap("2mock")

    return await (await Anki.getApi())["ankiGetNextTime" + i]();
  }

Open card browser and search with query

  • Name

api.ankiSearchCard(query)
  • Info

From reviewer UI, open Card browser and search for cards using the query

  • Usage

<button onclick="api.ankiSearchCard('deck:\"test\" {{hanzi}}')">Search this in Card browser</button>

View more examples in the PR #9247

Text to Speech

Synthesizes speech from text for immediate playback. The JS API is implemented on top of Android Text to Speech.

Select TTS language

  • Name

api.ankiTtsSetLanguage(Locale)

  • Parameters

loc

Locale: The locale describing the language to be used

  • Type of return value

int

Code indicating the support status for the locale

0 Denotes the language is available for the language by the locale, but not the country and variant.
1 Denotes the language is available for the language and country specified by the locale, but not the variant.
2 Denotes the language is available exactly as specified by the locale.
-1 Denotes the language data is missing.
-2 Denotes the language is not supported.
  • Info

Read more TextToSpeech#setLanguage

  • Usage

api.ankiTtsSetLanguage('en-US');

Change en-US to desired language.

Speak

  • Name

api.ankiTtsSpeak(text)

  • Parameters

text

CharSequence: The string of text to be spoken. No longer than getMaxSpeechInputLength() characters

queueMode

int: The queuing strategy to use, QUEUE_ADD or QUEUE_FLUSH

  • Type of return value

int

-1 ERROR
0 SUCCESS
  • Info

Speaks the text using the specified queuing strategy and speech parameters.

Read More TextToSpeech#speak(str)

  • Usage

api.ankiTtsSpeak(text)

If you add 1 to the second argument, it will wait until the previous Speak ends before speaking

api.ankiTtsSpeak(text, 1)

Set TTS speed

  • Name

api.ankiTtsSetSpeechRate(float)

  • Parameters

speechRate

float: Speech rate. 1.0 is the normal speech rate, lower values slow down the speech (0.5 is half the normal speech rate), greater values accelerate it (2.0 is twice the normal speech rate).

  • Type of return value

int

-1 ERROR
0 SUCCESS
  • Info

Read More TextToSpeech#setSpeechRate(float)

  • Usage

api.ankiTtsSetSpeechRate(0.8)

Set TTS pitch

  • Name

api.ankiTtsSetPitch(float)

  • Parameters

pitch

float: Speech pitch. 1.0 is the normal pitch, lower values lower the tone of the synthesized voice, greater values increase it.

  • Type of return value

int

-1 ERROR
0 SUCCESS
  • Info

Read more TextToSpeech#setPitch(float)

  • Usage

api.ankiTtsSetPitch(1.1)

Checks whether the TTS engine is busy speaking

  • Name

api.ankiTtsIsSpeaking()

  • Type of return value

boolean

  • Info

true if the TTS engine is speaking

Read moreTextToSpeech#isSpeaking()

  • Usage

let isSpeaking = api.ankiTtsIsSpeaking();

Stop speech

  • Name

api.ankiTtsStop()

  • Type of return value

int

-1 ERROR
0 SUCCESS
  • Info

Interrupts the current utterance (whether played or rendered to file) and discards other utterances in the queue

Read more TextToSpeech#stop()

  • Usage

api.ankiTtsStop()

View more in PR #8812

Some tips to improve card / deck development

If want to hide card's button / text in current card when reviewing on Anki Desktop / AnkiMobile then adding all code to if block can hide the things.
Note: Using this may give some problem when using AnkiWeb in Android Chrome, so to make available some functionality only to AnkiDroid app then wv in navigator.userAgent can be used.

 var UA = navigator.userAgent;

 var isMobile = /Android/i.test(UA);
 var isAndroidWebview = /wv/i.test(UA);

 if (isMobile) {
  // Here all AnkiDroid defined functions call. 
  // It will be hidden or not called on AnkiDesktop / AnkiMobile
  
   if (isAndroidWebview) {
     // Available only to AnkiDroid app only.

   } else {
     // Available to Chrome only (AnkiWeb opened in Android Chrome)
 
   }  
 }

For more view Window.navigator and Navigator userAgent Property

Other way to know if in AnkiDroid or other platform

 if (document.documentElement.classList.contains("android")) {
  // Hidden on AnkiDesktop / AnkiMobile

 } else {
  // Available to AnkiDesktop / AnkiMobile

 }

Adding progress bar to card template

Note: After AnkiDroid 2.12. Turn on fullscreen and also hide topbar from reviewer settings. More better implementation can be done for this.

Front/Back HTML

<div class="progress" id="progress">
    <div class="progress-bar" id="bar"></div>
</div>

<!-- anki-persistence -->
<script>
    // v0.5.2 - https://github.com/SimonLammer/anki-persistence/blob/62463a7f63e79ce12f7a622a8ca0beb4c1c5d556/script.js
    if (void 0 === window.Persistence) { var _persistenceKey = "github.com/SimonLammer/anki-persistence/", _defaultKey = "_default"; if (window.Persistence_sessionStorage = function () { var e = !1; try { "object" == typeof window.sessionStorage && (e = !0, this.clear = function () { for (var e = 0; e < sessionStorage.length; e++) { var t = sessionStorage.key(e); 0 == t.indexOf(_persistenceKey) && (sessionStorage.removeItem(t), e--) } }, this.setItem = function (e, t) { void 0 == t && (t = e, e = _defaultKey), sessionStorage.setItem(_persistenceKey + e, JSON.stringify(t)) }, this.getItem = function (e) { return void 0 == e && (e = _defaultKey), JSON.parse(sessionStorage.getItem(_persistenceKey + e)) }, this.removeItem = function (e) { void 0 == e && (e = _defaultKey), sessionStorage.removeItem(_persistenceKey + e) }) } catch (e) { } this.isAvailable = function () { return e } }, window.Persistence_windowKey = function (e) { var t = window[e], i = !1; "object" == typeof t && (i = !0, this.clear = function () { t[_persistenceKey] = {} }, this.setItem = function (e, i) { void 0 == i && (i = e, e = _defaultKey), t[_persistenceKey][e] = i }, this.getItem = function (e) { return void 0 == e && (e = _defaultKey), t[_persistenceKey][e] || null }, this.removeItem = function (e) { void 0 == e && (e = _defaultKey), delete t[_persistenceKey][e] }, void 0 == t[_persistenceKey] && this.clear()), this.isAvailable = function () { return i } }, window.Persistence = new Persistence_sessionStorage, Persistence.isAvailable() || (window.Persistence = new Persistence_windowKey("py")), !Persistence.isAvailable()) { var titleStartIndex = window.location.toString().indexOf("title"), titleContentIndex = window.location.toString().indexOf("main", titleStartIndex); titleStartIndex > 0 && titleContentIndex > 0 && titleContentIndex - titleStartIndex < 10 && (window.Persistence = new Persistence_windowKey("qt")) } }
</script>
<!----------->

<script>
        var cardCount = parseInt(api.ankiGetNewCardCount()) + parseInt(api.ankiGetLrnCardCount()) + parseInt(api.ankiGetRevCardCount());

        var totalCardCount = 1;
        if (Persistence.isAvailable()) {
            totalCardCount = Persistence.getItem("total");
            if (totalCardCount == null) {
                totalCardCount = cardCount;    // count set to total card count at first card, it will not change for current session
                Persistence.setItem("total", totalCardCount);
            }
        }

        // progress bar percentage
        var per = Math.trunc(100 - cardCount * 100 / totalCardCount);
        document.getElementById("bar").style.width = per + "%";
</script>

Card CSS

.progress {
    width: 100%;
    border-radius: 2px;
    background-color: #ddd;
}

.progress-bar {
    width: 1%;
    height: 12px;
    border-radius: 2px;
    background-color: limegreen;
}

Custom topbar design

Note: After AnkiDroid 2.12. Turn on fullscreen and also hide topbar from reviewer settings.

Add this to front/Back Side

<div style="display:inline;" class="card-count">
        <table style="width:28%; margin: 0 auto;">
            <tbody>
                <tr>
                <tr id="card_count_dot" class="card-count-dot">
                    <td style="color:#2196f3;">&#8226;</td>
                    <td style="color:#ea2322;">&#8226;</td>
                    <td style="color:#4caf50;">&#8226;</td>
                </tr>
                <tr class="card-count-num">
                    <td style="color:#2196f3;" id="newCard"></td>
                    <td style="color:#ea2322;" id="learnCard"></td>
                    <td style="color:#4caf50;" id="reviewCard"></td>
                </tr>
            </tbody>
        </table>
</div>
<div id="deck_title" class="title">{{Subdeck}}</div>
<div class="time-left" id="timeID"> </div>

<script>
 var UA = navigator.userAgent;

 var isMobile = /Android/i.test(UA);
 var isAndroidWebview = /wv/i.test(UA);

 if (isMobile) {
   // Here all AnkiDroid defined functions call. 
   // It will be hidden or not called on AnkiDesktop / AnkiMobile
   
   if (isAndroidWebview) {
        // Available only to AnkiDroid app only.

	document.getElementById("newCard").innerText = api.ankiGetNewCardCount();
        document.getElementById("learnCard").innerText = api.ankiGetLrnCardCount();
        document.getElementById("reviewCard").innerText = api.ankiGetRevCardCount();

        var t = api.ankiGetETA();
        document.getElementById("timeID").innerHTML = t + " mins.";
   }  
 } else {
	document.getElementById("card_count_dot").style.display = "none";
        document.getElementById("deck_title").style.display = "none";
}
</script>

Add this to Card CSS

/*card counts, title, time*/

.card-count {
    top: 0;
    right: 4px;
    position: absolute;
    display: none;
}

.card-count-num {
    color: black;
    font-size: 18px;
}

.card-count-dot {
    font-weight: bold;
    font-size: 24px;
}

.card-count-text {
    font-weight: light;
    font-size: 14px;
}

.title {
    top: 14px;
    left: 14px;
    position: absolute;
    font-size: 20px;
    color: grey;
    font-weight: bold;
}

.time-left {
    top: 36px;
    left: 14px;
    position: absolute;
    font-size: 14px;
    text-align: left;
    font-weight: bold;
    color: teal;
}

More features by silidev

This was too hard to maintain here, so I moved it here: https://github.com/ankidroid/Anki-Android/wiki/Extra-JS-API-functionality-by-silidev

Sample Decks

The implementation of above functionality can be found in this github repo. Anki Custom Card Layout

Linked issues & PR

#6521 apiVersioning and developerContact implementation for AnkiDroid functions call from WebView
#6377 apiVersioning and developerContact implementation for AndkiDroid functions call from WebView
#6307 Make available current card's and deck's details in WebView
#6388 Get next time for review in WebView
#6393 Get Cards info (Review, Lapses, Interval, Due, Queue) in WebView
#6766 JS API: Add Remaining Card Properties
#6784 Javascript API: Add ankiIsActiveNetworkMetered
#6747 Get Topbar shown status in card
#6387 Show options menu & navigation drawer using WebView
#6567 Night mode status in Card
#6470 Get value of fullscreen status in JavaScript
#6886 Automatically flip card if there is no cloze hint + detect ankidroid
#6386 Show Toast using JavaScript function in WebView
#8199 JS API to know if answer is displaying or question
#8500 Get deck name using JS API
#9247 JS API to open card browser and search with query
#9245 New JS API for bury & suspend card and bury & suspend note and tag card
#8812 New JavaScript api for TTS
#14564 convert synchronous js api to asynchronous and remove js interface

⚠️ **GitHub.com Fallback** ⚠️