> ## Documentation Index
> Fetch the complete documentation index at: https://docs.encorekit.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Gifts Button

> A trigger component that shows a gift entry at a reward moment and opens the reward sheet when the user taps it.

Gifts Button is a [trigger component](/publishers/triggers/overview): a "you have a gift" entry you place at a reward moment in your app, for example right after the user finishes a streak. Instead of your app opening the reward sheet on its own, the user sees the entry and opens the sheet when they choose to. Tapping it opens the same reward sheet that `show()` presents for that placement, with the same premium brand offers, each one a real perk a brand funds to acquire customers.

Its trigger ID is `giftsButton`. Everything on [Trigger components](/publishers/triggers/overview) applies to it. This page covers only what is specific to Gifts Button.

<Info>
  **Requires SDK 2.4.0+** on every platform. See [Minimum SDK versions](/publishers/triggers/overview#minimum-sdk-versions).
</Info>

## When to use it

Use Gifts Button on the screen where you would otherwise call `show()` for a [reward](/publishers/use-cases/post-action-reward), for example the screen that celebrates a finished streak. Mount it there **in place of** your own `show()` call for that moment, not as well as it. Keeping both can present the sheet twice.

Its use case defaults to the reward use case, so leave `useCase` out. Churn isn't supported yet: with the churn use case, Gifts Button renders nothing.

## What the user sees

* **An entry, only when there's a gift.** Gifts Button checks for offers for its use case and placement, the same check `show()` makes before it presents. With no offers, it renders nothing.
* **A tap opens the reward sheet.** The sheet is the normal one for that use case and placement, as if your app had presented it.
* **It stays while offers remain.** When the sheet closes, Gifts Button checks again and hides only when no offers are left. The user can't dismiss it.
* **It may open the sheet once by itself.** When an Encore test calls for it, Gifts Button can open its sheet on its own: at most once a day at each placement, only while your app is in the foreground, and never over another Encore sheet. Set `autoOpen` to off to stop it.

## Place it in your layout

Use the `GiftsButton` shorthand for your platform. Give it a placement ID and, optionally, your `appearance`, which colors the entry and never the sheet it opens. Encore picks the look:

<CodeGroup>
  ```swift iOS (SwiftUI) theme={null}
  import Encore
  import SwiftUI

  struct StreakCompleteView: View {
      var body: some View {
          VStack(spacing: 16) {
              Text("Streak complete")
              GiftsButton(
                  placement: "milestone_reached",
                  appearance: AppearanceConfig(accentColor: "#5671FF")
              )
          }
          .padding()
      }
  }
  ```

  ```swift iOS (UIKit) theme={null}
  import Encore
  import UIKit

  final class StreakCompleteViewController: UIViewController {
      private let stack = UIStackView()

      override func viewDidLoad() {
          super.viewDidLoad()
          stack.axis = .vertical
          stack.spacing = 16
          stack.translatesAutoresizingMaskIntoConstraints = false
          view.addSubview(stack)
          NSLayoutConstraint.activate([
              stack.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
              stack.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
              stack.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
          ])

          let gift = GiftsButtonView(
              placement: "milestone_reached",
              appearance: AppearanceConfig(accentColor: "#5671FF")
          )
          stack.addArrangedSubview(gift)
      }
  }
  ```

  ```kotlin Android (Compose) theme={null}
  import androidx.compose.foundation.layout.Arrangement
  import androidx.compose.foundation.layout.Column
  import androidx.compose.material3.Text
  import androidx.compose.runtime.Composable
  import androidx.compose.ui.unit.dp
  import com.encorekit.encore.AppearanceConfig
  import com.encorekit.encore.GiftsButton

  @Composable
  fun StreakCompleteScreen() {
      Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
          Text("Streak complete")
          GiftsButton(
              placement = "milestone_reached",
              appearance = AppearanceConfig(accentColor = "#5671FF"),
          )
      }
  }
  ```

  ```kotlin Android (View) theme={null}
  import android.os.Bundle
  import android.widget.LinearLayout
  import androidx.appcompat.app.AppCompatActivity
  import com.encorekit.encore.AppearanceConfig
  import com.encorekit.encore.GiftsButtonView

  class StreakCompleteActivity : AppCompatActivity() {
      override fun onCreate(savedInstanceState: Bundle?) {
          super.onCreate(savedInstanceState)
          val column = LinearLayout(this).apply { orientation = LinearLayout.VERTICAL }
          setContentView(column)

          val gift = GiftsButtonView(
              context = this,
              placement = "milestone_reached",
              appearance = AppearanceConfig(accentColor = "#5671FF"),
          )
          column.addView(gift)
      }
  }
  ```

  ```tsx React Native theme={null}
  import { View } from 'react-native';
  import { EncoreGiftsButton } from '@encorekit/react-native';

  export function StreakCompleteScreen() {
    return (
      <View>
        <EncoreGiftsButton
          placement="milestone_reached"
          appearance={{ accentColor: '#5671FF' }}
        />
      </View>
    );
  }
  ```

  ```dart Flutter theme={null}
  import 'package:flutter/material.dart';
  import 'package:encore_flutter/encore_flutter.dart';

  class StreakCompleteScreen extends StatelessWidget {
    const StreakCompleteScreen({super.key});

    @override
    Widget build(BuildContext context) {
      return Column(
        children: [
          const Text('Streak complete'),
          EncoreGiftsButton(
            placement: 'milestone_reached',
            appearance: AppearanceConfig(accentColor: '#5671FF'),
          ),
        ],
      );
    }
  }
  ```

  ```javascript Web SDK theme={null}
  import Encore from '@encorekit/web-sdk';

  // Call configure() first, as in the Web quickstart.
  const handle = Encore.triggers.giftsButton.mount(document.getElementById('gifts-button'), {
    placement: 'milestone_reached',
    appearance: { accentColor: '#5671FF' },
  });

  // Call this from your own teardown, when you remove the element.
  export function removeCard() {
    handle.unmount();
  }
  ```

  ```tsx Web SDK (React) theme={null}
  import { GiftsButton } from '@encorekit/web-sdk/react';

  // Render inside your EncoreProvider.
  export function StreakComplete() {
    return (
      <section>
        <h2>Streak complete</h2>
        <GiftsButton placement="milestone_reached" appearance={{ accentColor: '#5671FF' }} />
      </section>
    );
  }
  ```
</CodeGroup>

| Platform | Shorthand |
| - | - |
| iOS | `GiftsButton(placement:useCase:autoOpen:icon:appearance:publisherReward:onResult:alignment:)` (SwiftUI), `GiftsButtonView(placement:useCase:autoOpen:icon:appearance:publisherReward:onResult:alignment:)` (UIKit), each also settable as a property |
| Android | `GiftsButton(placement, useCase, autoOpen, icon, appearance, publisherReward, onResult, alignment)` (Compose), `GiftsButtonView(context, placement, useCase, autoOpen, icon, appearance, publisherReward, onResult)` (View, with `alignment` set as a property), or `<com.encorekit.encore.GiftsButtonView app:encorePlacement="milestone_reached" />` in XML |
| React Native | `<EncoreGiftsButton placement useCase autoOpen icon appearance publisherReward onResult alignment />` |
| Flutter | `EncoreGiftsButton(placement:, useCase:, autoOpen:, icon:, appearance:, publisherReward:, onResult:, alignment:)` |
| Web SDK | `Encore.triggers.giftsButton.mount(element, options)`, where `options` holds `placement`, `useCase`, `autoOpen`, `icon`, `appearance`, `publisherReward`, `onResult`, and `alignment` |
| Web SDK (React) | `<GiftsButton placement useCase autoOpen icon appearance publisherReward onResult alignment />` |

Each shorthand is the [trigger host](/publishers/triggers/quickstart#the-trigger-host) with the ID `giftsButton`. The [parameters table](/publishers/triggers/quickstart#parameters) gives every default.

## Your text

Pass `headline`, `subheadline` and `cta` to put your own words on the entry, such as the name of the moment the user just reached:

<CodeGroup>
  ```swift iOS theme={null}
  GiftsButton(placement: "milestone_reached")
      .headline("7 day streak!")
      .subheadline("Pick a gift from our partners")
  ```

  ```kotlin Android theme={null}
  GiftsButton(placement = "milestone_reached", headline = "7 day streak!", subheadline = "Pick a gift from our partners")
  ```

  ```tsx React Native theme={null}
  <EncoreGiftsButton placement="milestone_reached" headline="7 day streak!" subheadline="Pick a gift from our partners" />
  ```

  ```dart Flutter theme={null}
  EncoreGiftsButton(placement: 'milestone_reached', headline: '7 day streak!', subheadline: 'Pick a gift from our partners')
  ```

  ```javascript Web SDK theme={null}
  Encore.triggers.giftsButton.mount(element, { placement: 'milestone_reached', headline: '7 day streak!', subheadline: 'Pick a gift from our partners' });
  ```

  ```tsx Web SDK (React) theme={null}
  <GiftsButton placement="milestone_reached" headline="7 day streak!" subheadline="Pick a gift from our partners" />
  ```
</CodeGroup>

Any line you leave out shows the look's own copy, in the user's language, and every look Encore tests falls back to its own words. A look shows your `cta` only when it draws a button or a label, as `banner` and `pill` do. See [Your text](/publishers/triggers/overview#your-text) for the rules on length and line breaks.

## Looks

Encore picks the look, and can test several against each other. These are the looks it serves today. It draws each one from a look file it can change without an app update, so treat the heights as defaults rather than constants.

| Look | What it looks like | Height |
| - | - | - |
| `card` | An inline gift card with a gift icon, a title, a subtitle, and a chevron | 76 |
| `banner` | A wider banner with a title, a subtitle, a button, and a soft glow | 88 |
| `pulse` | An animated gift box with rings and a title | 112 |
| `pill` | A compact capsule with a gift icon and a label | 48 |
| `offerMarquee` | The card with a strip of the logos of the brands in the reward sheet, scrolling sideways under "Pick a gift, on us". With fewer than three logos to show, it is the plain card | 76 |
| `offerColumns` | The card with the same logos in two short columns scrolling in opposite directions, beside "Pick a gift, on us". With fewer than three logos to show, it is the plain card | 76 |
| `giftCard` | The card with a gift card face in place of the icon, under "Your gift card" | 76 |
| `stillWaiting` | A plain card titled "Your gift is still waiting", with no timer | 76 |

Heights are in points on iOS, `dp` on Android, and CSS pixels on the web. Each look is one control, named for assistive technology by its copy. The `pulse` animation and the scrolling logos of `offerMarquee` and `offerColumns` pause while the entry is off screen and stay still when the user has reduced motion turned on.

## Pass your prize and read the result

When your app pays its own prize for trying an offer, pass it to Gifts Button as `publisherReward`. It is the same value you would pass to `placement(id).publisherReward(...)` before `show()`, with the same rules: a `PublisherReward` on iOS, Android, and React Native, and an `EncorePublisherReward` on Flutter. On the web it is a `PublisherReward` with the same fields: `id`, `title`, and the optional `detail` and `iconUrl`. Gifts Button hands it to every sheet it opens. Pass `onResult` to receive the result of each sheet Gifts Button opens. It is called once per sheet, after the sheet closes, with the same result `show()` returns. Both are optional; without them Gifts Button behaves exactly as described earlier on this page.

On iOS, Android, React Native, and Flutter, grant the prize from `onResult` only, when `userConfirmedCompletion` is true on the result's claim, with the same grant key as [Reward for Trying](/publishers/guides/reward-for-trying): the transaction ID, or your user ID plus `campaignId` when it is missing.

<Warning>
  **On the web, the sheet doesn't show or grant your prize yet.** Gifts Button accepts `publisherReward` and passes it to the sheet, but the web sheet doesn't draw the promise or ask whether the user finished, and its result (a `ShowResult`) has no `userConfirmedCompletion` flag. A `claimed` result only means the user went to the brand, so never grant from it. Keep the claim's `transactionId` from `onResult`, as the web sample does, and grant from your server when the [offer\_completed webhook](/publishers/offers-api/guides/receive-completion-events) with that transaction ID confirms the completion.
</Warning>

### Other ways to learn about a claim

`onResult` is the main path, because it belongs to this one entry. Two other channels see the same claims:

* **The outcomes stream.** Every sheet Gifts Button opens resolves onto the same stream as your own `show()` calls: [`Encore.shared.outcomes`](/publishers/ios/sdk-reference/outcomes) on iOS, [`Encore.outcomes`](/publishers/android/sdk-reference/outcomes) on Android, [`Encore.shared.outcomes`](/publishers/flutter/sdk-reference/outcomes) on Flutter, and [`Encore.onOutcome()`](/publishers/react-native/sdk-reference/outcomes) on React Native. If your app already watches it, for analytics or entitlement sync, it sees Gifts Button's claims with no extra code. On the web, [`onStrictUnlockVerified()`](/publishers/web/sdk-reference/on-strict-unlock-verified) reports a claim the brand later confirms, in strict unlock mode.
* **The `offer_completed` webhook.** Your server hears about a completion the brand confirms, whichever surface the claim came from. See [Receive completion events](/publishers/offers-api/guides/receive-completion-events).

Pay the prize from one place only. The outcomes stream carries the same result `onResult` receives, so an app that grants from both pays twice.

<CodeGroup>
  ```swift iOS (SwiftUI) theme={null}
  GiftsButton(
      placement: "milestone_reached",
      publisherReward: PublisherReward(id: "coins_100", title: "100 coins"),
      onResult: { result in
          if let claim = result.claim, claim.userConfirmedCompletion {
              // Grant once per claim: key on the transaction, else on this user and campaign.
              grantCoins(100, dedupeKey: claim.transactionId ?? "\(userId):\(claim.campaignId)")
          }
      }
  )
  ```

  ```swift iOS (UIKit) theme={null}
  let gift = GiftsButtonView(placement: "milestone_reached")
  gift.publisherReward = PublisherReward(id: "coins_100", title: "100 coins")
  gift.onResult = { result in
      if let claim = result.claim, claim.userConfirmedCompletion {
          // Grant once per claim: key on the transaction, else on this user and campaign.
          grantCoins(100, dedupeKey: claim.transactionId ?? "\(userId):\(claim.campaignId)")
      }
  }
  ```

  ```kotlin Android (Compose) theme={null}
  import com.encorekit.encore.features.offers.PublisherReward

  GiftsButton(
      placement = "milestone_reached",
      publisherReward = PublisherReward(id = "coins_100", title = "100 coins"),
      onResult = { result ->
          val claim = result.claim
          if (claim != null && claim.userConfirmedCompletion) {
              // Grant once per claim: key on the transaction, else on this user and campaign.
              grantCoins(100, dedupeKey = claim.transactionId ?: "$userId:${claim.campaignId}")
          }
      },
  )
  ```

  ```kotlin Android (View) theme={null}
  import com.encorekit.encore.features.offers.PublisherReward

  val gift = GiftsButtonView(context = this, placement = "milestone_reached")
  gift.publisherReward = PublisherReward(id = "coins_100", title = "100 coins")
  gift.onResult = { result ->
      val claim = result.claim
      if (claim != null && claim.userConfirmedCompletion) {
          // Grant once per claim: key on the transaction, else on this user and campaign.
          grantCoins(100, dedupeKey = claim.transactionId ?: "$userId:${claim.campaignId}")
      }
  }
  ```

  ```tsx React Native theme={null}
  <EncoreGiftsButton
    placement="milestone_reached"
    publisherReward={{ id: 'coins_100', title: '100 coins' }}
    onResult={(result) => {
      if (result.status === 'presented' && result.claim?.userConfirmedCompletion) {
        // Grant once per claim: key on the transaction, else on this user and campaign.
        const claim = result.claim;
        grantCoins(100, claim.transactionId ?? `${userId}:${claim.campaignId}`);
      }
    }}
  />
  ```

  ```dart Flutter theme={null}
  EncoreGiftsButton(
    placement: 'milestone_reached',
    publisherReward: const EncorePublisherReward(id: 'coins_100', title: '100 coins'),
    onResult: (result) {
      final claim = result.claim;
      if (claim != null && claim.userConfirmedCompletion) {
        // Grant once per claim: key on the transaction, else on this user and campaign.
        grantCoins(100, claim.transactionId ?? '$userId:${claim.campaignId}');
      }
    },
  )
  ```

  ```javascript Web SDK theme={null}
  const handle = Encore.triggers.giftsButton.mount(document.getElementById('gifts-button'), {
    placement: 'milestone_reached',
    publisherReward: { id: 'coins_100', title: '100 coins' },
    onResult: (result) => {
      // The same result placement(id).show() resolves to. A claim isn't a completion:
      // keep its transaction ID and grant when the offer_completed webhook confirms it.
      if (result.status === 'claimed' && result.transactionId) {
        saveClaim(result.transactionId);
      }
    },
  });
  ```

  ```tsx Web SDK (React) theme={null}
  <GiftsButton
    placement="milestone_reached"
    publisherReward={{ id: 'coins_100', title: '100 coins' }}
    onResult={(result) => {
      if (result.status === 'claimed' && result.transactionId) saveClaim(result.transactionId);
    }}
  />
  ```
</CodeGroup>

Changing `publisherReward` on a mounted Gifts Button applies to the next sheet it opens.

## After a claim

Encore decides what the spot shows after the user claims an offer, and can test the choices against each other. Usually Gifts Button turns into a [Claim Reminder](/publishers/triggers/claim-reminder): the same spot reminds the user to finish that claim, with a countdown, and a tap opens the reward sheet with that offer in it. It turns back into Gifts Button when any of these happens:

* **The claim is completed.** Encore confirms the user finished it.
* **The reminder window ends.** That is 24 hours after the claim by default; Encore sets it with the look.
* **The user taps ×.** Every claim it was reminding about is hidden on this device. A claim made later brings the reminder back.
* **The offer leaves the sheet.** For example, the brand paused the campaign, or the campaign reached its cap.

Any claim the user makes in an Encore sheet counts, including one from your own `show()` call, as long as this placement's sheet still lists that offer. The SDK keeps the user's claims on the device, so the reminder also shows after the app reopens, until one of those four things ends it. The spot's request for its look includes whether a claim is open, so Gifts Button doesn't flash first. A claim counts as open while it's inside the reminder window last served at this placement, which the device remembers across launches; until one is served there, that's 24 hours.

The reminder in the spot keeps its own look and analytics. Don't also mount a Claim Reminder at the same spot: the user could see the reminder twice.

**The height changes.** Reminder looks are 76 points tall (pixels on the web), and the `featured` look is 85. Gifts Button's looks are 48 to 112. The entry takes its new height at once while the content fades across, so leave room in your layout for it to grow or shrink.

## After a completion

A claim is completed when Encore confirms the user finished the offer. On iOS and Android, a user telling the sheet they finished it counts too. An expired or failed claim is not a completion. The entry asks Encore about the user's newest open claims when the screen opens and when the app comes back to the foreground, so a completed claim stops showing as a reminder.

After a completion, Encore decides what the spot shows for the rest of the user's day, and can test the choices against each other:

* **Gifts Button** while its sheet has offers, as before.
* **Nothing**, until midnight on the user's device or until they claim another offer, whichever comes first.
* **A thanks look**: "Nice work, you finished it" with "More gifts are waiting" and a "See gifts" button, and no logos. A tap opens the reward sheet as Gifts Button does, and the spot goes back to Gifts Button. So do a new claim and midnight on the user's device. Its events carry the layout `thanks`.

There is nothing to set: the behavior comes with the look Encore serves.

## What Encore controls

The served look sets the layout, the height, the copy (in the user's language) for each line you don't [set yourself](/publishers/triggers/overview#your-text), the default colors, and what the spot shows [after a claim](#after-a-claim) and [after a completion](#after-a-completion). Encore can test several looks against each other, so different users can see a different entry. Your `appearance` colors always win, in the [color order](/publishers/triggers/overview#colors).

The entry never names the reward.

## Analytics

Gifts Button reports its own impressions and taps to Encore's analytics. You don't send any events.

## Related

* [Trigger components](/publishers/triggers/overview): how trigger components work, heights, and colors.
* [Quickstart](/publishers/triggers/quickstart) and [Experiments and looks](/publishers/triggers/experiments).
* [Reward Users](/publishers/use-cases/post-action-reward): the use case whose sheet Gifts Button opens.
* [Localization](/publishers/concepts/localization): how the entry's copy reaches the user's language.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.