> For the complete documentation index, see [llms.txt](https://help.usejimo.com/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://help.usejimo.com/docs/build/positioning/targeting-on-dynamic-apps.md).

# Targeting on dynamic apps

***

### Quick Access

* [SPA navigation](#spa-navigation)
* [Dynamic CSS selectors](#dynamic-css-selectors)
  * [Attribute selectors](#attribute-selectors)
  * [Element text](#element-text)
  * [Safety net](#safety-net)
* [Selector cheat sheet](#selector-cheat-sheet)

***

## What this page covers

Some applications make positioning harder than others. When the URL changes without a full page reload, or when CSS class names are regenerated on every build, Jimo loses track of the element an experience is attached to.

This page covers those two cases. For the position parameters, the element picker and the manual selector, start with [Positioning](/docs/build/positioning.md).

### Two problems, one symptom

Both show up in the dashboard the same way, an experience that does not display. They have different causes and different fixes.

<figure><img src="https://2794860263-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKzAcDWQbK1gKbpra7bkb%2Fuploads%2FoGOy7Aq3IXQkwhyR35Fz%2FDYN-01-two-problems.png?alt=media&amp;token=f81fffc5-9717-4a2a-9514-527e48b0e58e" alt=""><figcaption></figcaption></figure>

| Problem                   | What happens                                                                                                                                        | Fix                                                                                           |
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| **SPA navigation**        | The URL changes client side without reloading the page, so a trigger based on the URL does not reliably re-fire                                     | Trigger on the presence of an element, see [SPA navigation](#spa-navigation)                  |
| **Dynamic CSS selectors** | Class names and ids like `header_stack_x7Gz` or `data-id="1xfr2w6"` are regenerated on every build, so the selector Jimo memorized no longer exists | Target the stable part of the identifier, see [Dynamic CSS selectors](#dynamic-css-selectors) |

{% hint style="success" %}

#### Recommended approach

* **Best practice:** rework your frontend to add **static identifiers** to all elements you need to target with Jimo.
* Use **explicit `.class` names** or **`[data-test-id]` tags**.

⇒ This ensures consistent targeting across all scenarios and is the only fully reliable solution. Everything below is a workaround for when you cannot touch the code yet.
{% endhint %}

***

## SPA navigation

In a single-page application, moving from one view to another does not reload the page. Instead of triggering on the URL, trigger on the presence of an element that only exists on the target view.

1. **Open the trigger settings.** In your experience, go to **Show on > On event**.
2. **Add an Element condition.** Choose **Is present**, then select an element unique to the target view, a page title or a main container, not a global element like the header.
3. **Save.** The experience re-fires whenever that element appears in the DOM, independently of the URL.

<figure><img src="https://2794860263-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKzAcDWQbK1gKbpra7bkb%2Fuploads%2FjFEfet1N8A6YUrvEHLbn%2FDYN-02-spa-trigger-path.png?alt=media&amp;token=fcbed423-a130-4b95-b3be-42ca26ac7f65" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
Pick an anchor **specific to the view**. An element present on every page will make the experience fire everywhere.
{% endhint %}

The same **Element** condition is available in [Steps Triggers](/docs/build/triggers-and-conditions/steps-triggers.md) to move between steps, and in [Success Tracker events](/docs/analyze/success-tracker/events.md) to build a no-code event you can reuse across several experiences.

***

## Dynamic CSS selectors

In some cases, positioning in Jimo can be limited by how your website is structured, especially when elements are defined with **dynamic CSS selectors**.

<figure><img src="https://2794860263-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKzAcDWQbK1gKbpra7bkb%2Fuploads%2F2E7PGXDtjwAOUKgE7lOS%2Fimage.png?alt=media&amp;token=b5905b74-650a-4e22-8033-d23cbc0c59ff" alt=""><figcaption></figcaption></figure>

If your UI uses dynamic identifiers that change constantly, Jimo's auto-selection will often break, and the element may be "lost".

<figure><img src="https://2794860263-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKzAcDWQbK1gKbpra7bkb%2Fuploads%2FRxCm0oapfeF6FseHbrQd%2Fimage.png?alt=media&amp;token=eaf1127d-dba4-4091-ad89-bcfa503690b5" alt=""><figcaption></figcaption></figure>

### Attribute selectors

Native CSS attribute selectors let you match **only the stable part** of a dynamic class or id. Enter them in the **CSS Selector** field of the [Manual Selector](/docs/build/positioning.md#manual-selector).

```
[class^="header_stack_"]   /* starts with */
[class*="headerStack"]     /* contains    */
[class$="_active"]         /* ends with   */
```

| Selector                       | Meaning                          | Example                                             |
| ------------------------------ | -------------------------------- | --------------------------------------------------- |
| `[id^="prefix"]`               | id **starts with** `prefix`      | `[id^="mp-input"]` matches `mp-input-1234-control`  |
| `[id$="suffix"]`               | id **ends with** `suffix`        | `[id$="control"]` matches `mp-input-1234-control`   |
| `[id*="substring"]`            | id **contains** `substring`      | `[id*="mp-input"]` matches `anything-mp-input-1234` |
| `[id^="prefix"][id$="suffix"]` | combines both, the most reliable | `[id^="mp-input"][id$="control"]`                   |

The same syntax works on `class`, on `data-*` and on any other attribute. Combine it with the tag name to narrow the match down:

```
button[class*="primaryCta"]
nav [class*="menuItem"]
```

<figure><img src="https://2794860263-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKzAcDWQbK1gKbpra7bkb%2Fuploads%2Fuww53tOPtTFuSreT26wR%2FDYN-03-attribute-selector-anatomy.png?alt=media&amp;token=6221220d-a507-4d9d-8b6d-5917f51ba7bd" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}

#### Limits

* If too many elements match the pattern, add a constraint: a parent selector, a tag name, or a second attribute selector.
* Use the [In-App Builder](/docs/build/builders/in-app-builder.md) to check **how many elements match** before publishing. One is the answer you want.
* This stays less reliable than a static identifier. Treat it as a bridge, not as the destination.
  {% endhint %}

<mark style="color:blue;">Full CSS reference:</mark> [<mark style="color:blue;">MDN attribute selectors</mark>](https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors)<mark style="color:blue;">.</mark>

### Element text

When the element contains visible text, use the **Element Text** field of the manual selector, on its own or combined with a simple CSS selector like `.btn`, `.menu` or `.title` to refine matches if the text appears in several places.

<figure><img src="https://2794860263-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKzAcDWQbK1gKbpra7bkb%2Fuploads%2FvVoOlyytkdtsFEYcNSJ6%2Fimage.png?alt=media&amp;token=8d16ffef-7801-41fb-a0e4-82914704f737" alt=""><figcaption></figcaption></figure>

{% hint style="danger" %}

#### Important limitations

* If your interface is **translated**, text-based selectors will not match across all languages. You will need to **duplicate and reposition** your experiences for each language version and adjust audience targeting accordingly.
* If your elements have no generic CSS classes or static identifiers *and* your UI is translated, then the **only viable solution** is to update your frontend to add unique identifiers.
  {% endhint %}

### Safety net

Enable **Skip if target element not found** on steps whose anchor may not always be present, so the rest of the experience continues instead of stalling.

{% hint style="info" %}
**Pro tip:** the **Strictness** slider in the [Positioning](broken://pages/72d5301c2287f9a458733b903e02dbea3e661255#strictness-option) settings is the fastest way to strip `:nth-child()` rules and generated ids out of an auto-generated selector before editing it by hand.
{% endhint %}

***

## Ask your developers

The durable fix costs one pass on the elements your experiences actually rely on. Share this with your technical team:

{% hint style="info" %}
Add a stable `data-test-id` attribute on the key elements we want to use with Jimo: action buttons, menu items, view anchors. Jimo then targets `[data-test-id="..."]`, which does not break on deploys or across languages.
{% endhint %}

***

## Selector cheat sheet

<figure><img src="https://2794860263-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FKzAcDWQbK1gKbpra7bkb%2Fuploads%2Fa4GDKrVbhTMKmU6ERvyO%2FDYN-04-decision-tree.png?alt=media&amp;token=f9cbbc37-8f11-4f42-8591-4155aafd14f4" alt=""><figcaption></figcaption></figure>

| Need                            | Selector                          |
| ------------------------------- | --------------------------------- |
| Stable identifier, the ideal    | `[data-test-id="save-btn"]`       |
| Class starts with               | `[class^="header_"]`              |
| Class contains                  | `[class*="headerStack"]`          |
| Class ends with                 | `[class$="_active"]`              |
| Tag plus partial class          | `button[class*="primaryCta"]`     |
| Descendant                      | `nav [class*="menuItem"]`         |
| Two constraints combined        | `[id^="mp-input"][id$="control"]` |
| Visible text, non-translated UI | `.btn` plus Element Text `Save`   |

***

## Related pages

* [Positioning](/docs/build/positioning.md)
* [Steps Triggers](/docs/build/triggers-and-conditions/steps-triggers.md)
* [Trigger on the right spot (Show on)](/docs/publish/trigger-on-the-right-spot-show-on.md)
* [Success Tracker events](/docs/analyze/success-tracker/events.md)
* [In-App Builder](/docs/build/builders/in-app-builder.md)
* [Troubleshooting](/docs/settings/troubleshooting.md)
