Search code examples
cssbotframeworkadaptive-cardsweb-chat

How to use CSS styling on adaptive cards?


I'm trying to use CSS to style the adaptive cards on a more fine grained level, however the similar names of the default CSS classes makes it difficult to target individual elements.

I've attempted to try target it with specific parameters, but they still end up crossing rules to some extent. I've tried using unique ids or uniqueCSSSelectors during the adaptive card build process, but it seems they are all stripped out by the time it makes it to the front end.

Here are some examples of what I've tried so far.

li div .content .attachment .attachment div .ac-container:not(:first-child) .ac-columnSet:hover{
    background: #e6eaed;
    color: #323232;
}

li div .content .attachment .attachment div .ac-container:not(:first-child) .ac-columnSet:hover .ac-container .ac-textBlock p{
    color: #323232;
}```

Solution

  • First, I want to mention that this ability is already a feature request that is being worked on in the BotFramework-WebChat repo. I don't know the ETA on this, so don't plan on it being soon. But, do keep an eye out.

    This is doable with a little hacking. In short, these are your steps:

    1. Create a "trigger" property in your adaptive card(s) and assign it a unique value.
    2. Create an activityMiddleware that looks for the trigger value. When received, then update the element that will hold the incoming adaptive card by attaching an id.
    3. Add pure CSS to your html that styles the card based on the attached id in step 2.
    4. Add the activityMiddleware object to direct line.

    Hope of help!


    Here's example code:

    mainDialog.js - Adaptive card to be sent from my bot

      async basicAdaptiveCard ( stepContext ) {
        const card = {
          "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
          "type": "AdaptiveCard",
          "version": "1.0",
          "trigger": "ServiceCardTrigger",
          "body": [
            {
              "type": "TextBlock",
              "text": "Hi!! How can I help you today?",
              "weight": "Bolder",
              "size": "Medium"
            }
          ],
          "actions": [
            {
              "type": "Action.Submit",
              "title": "Placeholder Message",
              "data": "close"
            }
          ]
        }
    
        const ac_card = CardFactory.adaptiveCard( card );
        await stepContext.context.sendActivity(
          {
            attachments: [ ac_card ]
          }
        );
        return { status: DialogTurnStatus.waiting };
      }
    

    index.html - Showing only the essential bits. In this I'm tracking via a button with the value that includes "Service details". It's simplistic, but works for the demo. Update as necessary.

    <style>
      #ServiceCard {
        background-color: #e6eaed;
        color: #323232;
      }
    
      #ServiceCard:hover p {
        color: red
      }
    </style>
    
    [...]
    
    <script type="text/babel">
      [...]
    
      const activityMiddleware = () => next => card => {
        const { activity: { from, type, attachments } } = card;
        if (type === 'message' && from.role === 'bot') {
          if(attachments && attachments[0].content.trigger === 'ServiceCardTrigger') {
            let children = document.getElementsByClassName('ac-adaptiveCard');
            for (let i = 0, j = children.length; i <= j; i++) {
              if(i === j - 1) {
                let child = children[i];
                if(child.lastChild.innerHTML.includes('Service details')) {
                  child.id = 'ServiceCard';
                }
              }
            };
          }
        } 
        else {
          return next(card)
        }
      }
    
      [...]
    
      window.ReactDOM.render(
        <ReactWebChat
          activityMiddleware={ activityMiddleware }
          directLine={ directLine }
          username={'johndoe'}
        />,
        document.getElementById( 'webchat' )
      );
    
      [...]
    </script>
    

    enter image description here